diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 487c7ff750..d6fd518862 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -14729,8 +14729,8 @@ SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab;
    partition through the last peer of the current row.  This is
    likely to give unhelpful results for <function>last_value</function> and
    sometimes also <function>nth_value</function>.  You can redefine the frame by
-   adding a suitable frame specification (<literal>RANGE</literal> or
-   <literal>ROWS</literal>) to the <literal>OVER</literal> clause.
+   adding a suitable frame specification (<literal>RANGE</literal>,
+   <literal>ROWS</literal> or <literal>GROUPS</literal>) to the <literal>OVER</literal> clause.
    See <xref linkend="syntax-window-functions"/> for more information
    about frame specifications.
   </para>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 8a3e86b6db..3381dca3f2 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -859,8 +859,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
     The <replaceable class="parameter">frame_clause</replaceable> can be one of
 
 <synopsis>
-{ RANGE | ROWS } <replaceable>frame_start</replaceable>
-{ RANGE | ROWS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable>
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable class="parameter">frame_exclusion_clause</replaceable> ]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable class="parameter">frame_exclusion_clause</replaceable> ]
 </synopsis>
 
     where <replaceable>frame_start</replaceable> and <replaceable>frame_end</replaceable> can be
@@ -874,6 +874,16 @@ CURRENT ROW
 UNBOUNDED FOLLOWING
 </synopsis>
 
+    and the optional <replaceable class="parameter">frame_exclusion_clause</replaceable> can be
+    one of
+
+<synopsis>
+EXCLUDE CURRENT ROW
+EXCLUDE GROUP
+EXCLUDE TIES
+EXCLUDE NO OTHERS
+</synopsis>
+
     If <replaceable>frame_end</replaceable> is omitted it defaults to <literal>CURRENT
     ROW</literal>.  Restrictions are that
     <replaceable>frame_start</replaceable> cannot be <literal>UNBOUNDED FOLLOWING</literal>,
@@ -894,25 +904,37 @@ UNBOUNDED FOLLOWING
     In general, <literal>UNBOUNDED PRECEDING</literal> means that the frame
     starts with the first row of the partition, and similarly
     <literal>UNBOUNDED FOLLOWING</literal> means that the frame ends with the last
-    row of the partition (regardless of <literal>RANGE</literal> or <literal>ROWS</literal>
-    mode).  In <literal>ROWS</literal> mode, <literal>CURRENT ROW</literal>
+    row of the partition (regardless of <literal>RANGE</literal>, <literal>ROWS</literal>
+    or <literal>GROUPS</literal> mode).  In <literal>ROWS</literal> mode, <literal>CURRENT ROW</literal>
     means that the frame starts or ends with the current row; but in
-    <literal>RANGE</literal> mode it means that the frame starts or ends with
+    <literal>RANGE</literal> or <literal>GROUPS</literal> mode it means that the frame starts or ends with
     the current row's first or last peer in the <literal>ORDER BY</literal> ordering.
     The <replaceable>value</replaceable> <literal>PRECEDING</literal> and
-    <replaceable>value</replaceable> <literal>FOLLOWING</literal> cases are currently only
-    allowed in <literal>ROWS</literal> mode.  They indicate that the frame starts
-    or ends with the row that many rows before or after the current row.
-    <replaceable>value</replaceable> must be an integer expression not
-    containing any variables, aggregate functions, or window functions.
-    The value must not be null or negative; but it can be zero, which
-    selects the current row itself.
+    <replaceable>value</replaceable> <literal>FOLLOWING</literal> cases differ depending on
+    whether the frame clause is in <literal>ROWS</literal>, <literal>RANGE</literal>or <literal>GROUPS</literal> mode. In
+    <literal>ROWS</literal> mode, they indicate that the frame starts or ends with the row that
+    many rows before or after the current row. In <literal>RANGE</literal> mode, they indicate that
+    the frame starts or ends when the ORDER BY column's value for each row is within the bounds
+    specified by <replaceable>value</replaceable> for both the start and the end of the frame. In <literal>GROUPS</literal> mode,
+    they indicate the number of changes to the value of the ORDER BY columns (i.e., groups of peers).
+    In <literal>ROWS</literal> or <literal>GROUPS</literal> mode, <replaceable>value</replaceable> must be an integer expression not
+    containing any variables, aggregate functions, or window functions.In <literal>RANGE</literal> mode,
+    there must be exactly one ORDER BY column of a supported type. In all three modes, the value must not be null or
+    negative; but it can be zero, which just selects the current row itself.
+   </para>
+
+   <para>
+    For the <replaceable class="parameter">frame_exclusion_clause</replaceable>, <literal>EXCLUDE CURRENT ROW</literal>
+    excludes the current row from the frame. <literal>EXCLUDE TIES</literal> excludes any peers of the current row from the
+    frame. <literal>EXCLUDE GROUP</literal> excludes both the current row and any peers of the current row from the frame.
+    <literal>EXCLUDE NO OTHERS</literal> does nothing, but is provided in order to optionally document the intention
+    not to exclude any other rows.
    </para>
 
    <para>
     Beware that the <literal>ROWS</literal> options can produce unpredictable
     results if the <literal>ORDER BY</literal> ordering does not order the rows
-    uniquely.  The <literal>RANGE</literal> options are designed to ensure that
+    uniquely.  The <literal>RANGE</literal> and <literal>GROUPS</literal> options are designed to ensure that
     rows that are peers in the <literal>ORDER BY</literal> ordering are treated
     alike; all peer rows will be in the same frame.
    </para>
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index a938a21334..162ee04228 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -1805,8 +1805,8 @@ FROM generate_series(1,10) AS s(i);
     and the optional <replaceable class="parameter">frame_clause</replaceable>
     can be one of
 <synopsis>
-{ RANGE | ROWS } <replaceable>frame_start</replaceable>
-{ RANGE | ROWS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable>
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable class="parameter">frame_exclusion_clause</replaceable> ]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable class="parameter">frame_exclusion_clause</replaceable> ]
 </synopsis>
     where <replaceable>frame_start</replaceable> and <replaceable>frame_end</replaceable> can be
     one of
@@ -1817,6 +1817,13 @@ CURRENT ROW
 <replaceable>value</replaceable> FOLLOWING
 UNBOUNDED FOLLOWING
 </synopsis>
+    where the optional <replaceable>frame_exclusion_clause</replaceable> can be one of
+<synopsis>
+EXCLUDE CURRENT ROW
+EXCLUDE GROUP
+EXCLUDE TIES
+EXCLUDE NO OTHERS
+</synopsis>
    </para>
 
    <para>
@@ -1857,8 +1864,8 @@ UNBOUNDED FOLLOWING
     the set of rows constituting the <firstterm>window frame</firstterm>, which is a
     subset of the current partition, for those window functions that act on
     the frame instead of the whole partition.  The frame can be specified in
-    either <literal>RANGE</literal> or <literal>ROWS</literal> mode; in either case, it
-    runs from the <replaceable>frame_start</replaceable> to the
+    either <literal>RANGE</literal>, <literal>ROWS</literal> or <literal>GROUPS</literal> mode;
+    in each case, it runs from the <replaceable>frame_start</replaceable> to the
     <replaceable>frame_end</replaceable>.  If <replaceable>frame_end</replaceable> is omitted,
     it defaults to <literal>CURRENT ROW</literal>.
    </para>
@@ -1871,7 +1878,7 @@ UNBOUNDED FOLLOWING
    </para>
 
    <para>
-    In <literal>RANGE</literal> mode, a <replaceable>frame_start</replaceable> of
+    In <literal>RANGE</literal> or <literal>GROUPS</literal> mode, a <replaceable>frame_start</replaceable> of
     <literal>CURRENT ROW</literal> means the frame starts with the current row's
     first <firstterm>peer</firstterm> row (a row that <literal>ORDER BY</literal> considers
     equivalent to the current row), while a <replaceable>frame_end</replaceable> of
@@ -1882,13 +1889,30 @@ UNBOUNDED FOLLOWING
 
    <para>
     The <replaceable>value</replaceable> <literal>PRECEDING</literal> and
-    <replaceable>value</replaceable> <literal>FOLLOWING</literal> cases are currently only
-    allowed in <literal>ROWS</literal> mode.  They indicate that the frame starts
-    or ends the specified number of rows before or after the current row.
-    <replaceable>value</replaceable> must be an integer expression not
-    containing any variables, aggregate functions, or window functions.
-    The value must not be null or negative; but it can be zero, which
-    just selects the current row.
+    <replaceable>value</replaceable> <literal>FOLLOWING</literal> cases, when used
+    in <literal>ROWS</literal> mode, indicate that the frame starts or ends the specified
+    number of rows before or after the current row. In <literal>ROWS</literal> mode,
+    <replaceable>value</replaceable> must be an integer expression not containing any variables,
+    aggregate functions, or window functions.
+    When used in <literal>RANGE</literal> mode, they indicate that the frame starts or ends when the value of
+    each row's ORDER BY column is within the start value and end value bounds. In both modes,
+    the value must not be null or negative; but it can be zero, which just selects the current row.
+   </para>
+
+   <para>
+    In <literal>GROUPS</literal> mode, <replaceable>value</replaceable> <literal>PRECEDING</literal> and
+    <replaceable>value</replaceable> <literal>FOLLOWING</literal> cases indicate that the frame starts or ends the specified
+    number of <literal>window framing groups</literal> before or after the current <literal>window framing group</literal>.
+    Two rows are in the same <literal>window framing group</literal> if they are peers, (i.e., their ORDER BY column values
+    match). This mode allows the selection of a frame by the number of changes to the ORDER BY columns.
+   </para>
+
+   <para>
+    For the <replaceable class="parameter">frame_exclusion_clause</replaceable>, <literal>EXCLUDE CURRENT ROW</literal>
+    excludes the current row from the frame. <literal>EXCLUDE TIES</literal> excludes any peers of the current row from the
+    frame. <literal>EXCLUDE GROUP</literal> excludes both the current row and any peers of the current row from the frame.
+    <literal>EXCLUDE NO OTHERS</literal> does nothing, but is provided in order to optionally document the intention not to
+    exclude any other rows.
    </para>
 
    <para>
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index 8e746f36d4..20d61f3780 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -498,7 +498,7 @@ T616	Null treatment option for LEAD and LAG functions			NO
 T617	FIRST_VALUE and LAST_VALUE function			YES	
 T618	NTH_VALUE function			NO	function exists, but some options missing
 T619	Nested window functions			NO	
-T620	WINDOW clause: GROUPS option			NO	
+T620	WINDOW clause: GROUPS option			YES	
 T621	Enhanced numeric functions			YES	
 T631	IN predicate with one list element			YES	
 T641	Multiple column assignment			NO	only some syntax variants supported
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 0afb1c83d3..a2c9e0d8f1 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -37,6 +37,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
 #include "miscadmin.h"
@@ -180,7 +181,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static bool row_is_in_frame(WindowAggState *winstate, int64 pos,
+static bool row_is_in_group(WindowAggState *winstate, int64 currpos, int64 slotpos,
+				int64 offset, bool preceding, bool end);
+static int row_is_in_frame(WindowAggState *winstate, int64 pos,
 				TupleTableSlot *slot);
 static void update_frameheadpos(WindowObject winobj, TupleTableSlot *slot);
 static void update_frametailpos(WindowObject winobj, TupleTableSlot *slot);
@@ -683,9 +686,6 @@ eval_windowaggregates(WindowAggState *winstate)
 	temp_slot = winstate->temp_slot_1;
 
 	/*
-	 * Currently, we support only a subset of the SQL-standard window framing
-	 * rules.
-	 *
 	 * If the frame start is UNBOUNDED_PRECEDING, the window frame consists of
 	 * a contiguous group of rows extending forward from the start of the
 	 * partition, and rows only enter the frame, never exit it, as the current
@@ -737,15 +737,17 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * the result values that were previously saved at the bottom of this
 	 * function.  Since we don't know the current frame's end yet, this is not
 	 * possible to check for fully.  But if the frame end mode is UNBOUNDED
-	 * FOLLOWING or CURRENT ROW, and the current row lies within the previous
-	 * row's frame, then the two frames' ends must coincide.  Note that on the
-	 * first row aggregatedbase == aggregatedupto, meaning this test must
-	 * fail, so we don't need to check the "there was no previous row" case
-	 * explicitly here.
+	 * FOLLOWING or CURRENT ROW, no exclusion clause is specified, we are not
+	 * in GROUPS BETWEEN with values mode, and the current row lies within the
+	 * previous row's frame, then the two frames' ends must coincide.  Note that on
+	 * the first row aggregatedbase == aggregatedupto, meaning this test must fail, so we
+	 * don't need to check the "there was no previous row" case explicitly here.
 	 */
 	if (winstate->aggregatedbase == winstate->frameheadpos &&
 		(winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
 								   FRAMEOPTION_END_CURRENT_ROW)) &&
+		!(winstate->frameOptions & FRAMEOPTION_EXCLUSION) &&
+		!(winstate->frameOptions & FRAMEOPTION_GROUPS_BETWEEN) &&
 		winstate->aggregatedbase <= winstate->currentpos &&
 		winstate->aggregatedupto > winstate->currentpos)
 	{
@@ -766,6 +768,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	 - if we're processing the first row in the partition, or
 	 *	 - if the frame's head moved and we cannot use an inverse
 	 *	   transition function, or
+	 *	 - we are in RANGE BETWEEN with values mode, or
+	 *	 - we are in GROUPS BETWEEN with values mode, or
+	 *	 - we are in EXCLUDE CURRENT ROW/EXCLUDE TIES mode, or
 	 *	 - if the new frame doesn't overlap the old one
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
@@ -780,6 +785,9 @@ eval_windowaggregates(WindowAggState *winstate)
 		if (winstate->currentpos == 0 ||
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
+			winstate->frameOptions & FRAMEOPTION_RANGE_BETWEEN ||
+			winstate->frameOptions & FRAMEOPTION_GROUPS_BETWEEN ||
+			winstate->frameOptions & FRAMEOPTION_EXCLUSION ||
 			winstate->aggregatedupto <= winstate->frameheadpos)
 		{
 			peraggstate->restart = true;
@@ -920,6 +928,8 @@ eval_windowaggregates(WindowAggState *winstate)
 	 */
 	for (;;)
 	{
+		int ret;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -928,9 +938,15 @@ eval_windowaggregates(WindowAggState *winstate)
 				break;			/* must be end of partition */
 		}
 
-		/* Exit loop (for now) if not in frame */
-		if (!row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot))
+		/*
+		 * Exit loop if no more rows can be in frame. Skip aggregation if current
+		 * row is not in frame.
+		 */
+		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
+		if (ret == -1)
 			break;
+		else if (ret == 0)
+			goto next_tuple;
 
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
@@ -951,6 +967,7 @@ eval_windowaggregates(WindowAggState *winstate)
 									peraggstate);
 		}
 
+		next_tuple:
 		/* Reset per-input-tuple context after each tuple */
 		ResetExprContext(winstate->tmpcontext);
 
@@ -1113,7 +1130,10 @@ begin_partition(WindowAggState *winstate)
 		int			readptr_flags = 0;
 
 		/* If the frame head is potentially movable ... */
-		if (!(winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING))
+		if (!(winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) ||
+			winstate->frameOptions & (FRAMEOPTION_RANGE_BETWEEN) ||
+			winstate->frameOptions & (FRAMEOPTION_GROUPS_BETWEEN) ||
+			winstate->frameOptions & (FRAMEOPTION_EXCLUSION))
 		{
 			/* ... create a mark pointer to track the frame head */
 			agg_winobj->markptr = tuplestore_alloc_read_pointer(winstate->buffer, 0);
@@ -1155,6 +1175,94 @@ begin_partition(WindowAggState *winstate)
 	 */
 	tuplestore_puttupleslot(winstate->buffer, winstate->first_part_slot);
 	winstate->spooled_rows++;
+
+	/*
+	 * In RANGE BETWEEN and GROUPS BETWEEN with values mode, pre-compute the lengths
+	 * of each window group by loading the partition and checking if each row is a peer
+	 * of the succeeding row. The number of groups is stored in winobj->winGroupsCount
+	 * and the number of rows in each group is stored in winobj->winGroupLen.
+	 *
+	 * In GROUPS BETWEEN mode, we also store copies of these pointers and move them forward
+	 * using the offset in order to keep track of the frame head/tail.
+	 */
+	if (winstate->frameOptions & (FRAMEOPTION_GROUPS_BETWEEN | FRAMEOPTION_RANGE_BETWEEN))
+	{
+		int64	i,
+				partitionSize;
+		int64	*lenptr;
+		int64	groupLenSize = 16;
+		bool	peers;
+		WindowObject currWinobj;
+
+		if (winstate->numaggs > 0)
+			currWinobj = winstate->agg_winobj;
+		else
+			currWinobj = (&(winstate->perfunc[0]))->winobj;
+		winstate->winGroupsCount = 1;
+		winstate->winGroupLen = palloc0(sizeof(int64) * groupLenSize);
+		lenptr = winstate->winGroupLen;
+		*lenptr = 1;
+		partitionSize = WinGetPartitionRowCount(currWinobj);
+
+		for (i = 0; i < partitionSize - 1; i++)
+		{
+			peers = WinRowsArePeers(currWinobj, i, i+1);
+			if (peers)
+				(*lenptr)++;
+			else
+			{
+				winstate->winGroupsCount++;
+				if (winstate->winGroupsCount > groupLenSize)
+				{
+					int64 prevSize = groupLenSize;
+
+					groupLenSize *= 2;
+					winstate->winGroupLen = repalloc(winstate->winGroupLen,
+													 sizeof(int64) * groupLenSize);
+					lenptr = winstate->winGroupLen;
+					lenptr += prevSize;
+				}
+				else
+					lenptr++;
+
+				*lenptr = 1;
+			}
+		}
+
+		/* Initialize frame head/tail pointers and lengths */
+		winstate->frameheadWinGroup = winstate->winGroupLen;
+		winstate->currWinGroup = winstate->winGroupLen;
+		winstate->frametailWinGroup = winstate->winGroupLen;
+		winstate->frametailpos = 0;
+		winstate->prevRowTotal = 0;
+		winstate->frameheadGroupsCount = 0;
+		winstate->frametailGroupsCount = 0;
+
+		/*
+		 * In value following mode, move frame head/tail to offset position
+		 */
+		if (winstate->frameOptions & FRAMEOPTION_GROUPS_BETWEEN &&
+			(winstate->frameOptions & FRAMEOPTION_START_VALUE_PRECEDING) == 0)
+		{
+			int64	offset = DatumGetInt64(winstate->startOffsetValue);
+			for (i = offset; i > 0; i--)
+			{
+				winstate->frameheadpos += *(winstate->frameheadWinGroup);
+				winstate->frameheadWinGroup++;
+			}
+		}
+		if (winstate->frameOptions & FRAMEOPTION_GROUPS_BETWEEN &&
+			(winstate->frameOptions & FRAMEOPTION_END_VALUE_PRECEDING) == 0)
+		{
+			int64	offset = DatumGetInt64(winstate->endOffsetValue);
+			for (i = 0; i < offset; i++)
+			{
+				winstate->frametailpos += *(winstate->frametailWinGroup);
+				winstate->frametailWinGroup++;
+				winstate->frametailGroupsCount++;
+			}
+		}
+	}
 }
 
 /*
@@ -1265,6 +1373,71 @@ release_partition(WindowAggState *winstate)
 		tuplestore_end(winstate->buffer);
 	winstate->buffer = NULL;
 	winstate->partition_spooled = false;
+
+	if (winstate->frameOptions & (FRAMEOPTION_GROUPS_BETWEEN | FRAMEOPTION_RANGE_BETWEEN) &&
+		winstate->winGroupsCount > 0)
+	{
+		pfree(winstate->winGroupLen);
+		winstate->winGroupsCount = 0;
+	}
+}
+
+/*
+ * row_is_in_group
+ * Determine whether a row is in range when in GROUPS BETWEEN with values
+ * mode.
+ *
+ * Compares the current position to the slot position and checks if they are
+ * within the specified window group offset.
+ */
+static bool row_is_in_group(WindowAggState *winstate, int64 currpos, int64 slotpos,
+				int64 offset, bool preceding, bool end)
+{
+	int64		i,
+				len = 0,
+				currGroup = 0,
+				slotGroup = 0;
+	int64		*lenptr = winstate->winGroupLen;
+
+	if (preceding)
+		offset = -offset;
+	/*
+	* Calculate the currpos window group, then the slotpos window group. If
+	* the slotpos group is outside of the offset bounds, return false.
+	*/
+	for (i = 0; i < winstate->winGroupsCount; i++, lenptr++)
+	{
+		len += *lenptr;
+		if (len > currpos)
+		{
+			currGroup = i;
+			break;
+		}
+	}
+	lenptr = winstate->winGroupLen;
+	len = 0;
+	for (i = 0; i < winstate->winGroupsCount; i++, lenptr++)
+	{
+		len += *lenptr;
+		if (len > slotpos)
+		{
+			slotGroup = i;
+			break;
+		}
+	}
+
+	currGroup += offset;
+	if (end)
+	{
+		if (slotGroup > currGroup)
+			return false;
+	}
+	else
+	{
+		if (slotGroup < currGroup)
+			return false;
+	}
+	return true;
 }
 
 /*
@@ -1275,12 +1448,16 @@ release_partition(WindowAggState *winstate)
  * The caller must have already determined that the row is in the partition
  * and fetched it into a slot.  This function just encapsulates the framing
  * rules.
+ *
+ * Returns:
+ * -1, if the row is out of frame and no succeeding rows can be in frame
+ * 0, if the row is out of frame but succeeding rows might be in frame
+ * 1, if the row is in frame
  */
-static bool
+static int
 row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot)
 {
 	int			frameOptions = winstate->frameOptions;
-
 	Assert(pos >= 0);			/* else caller error */
 
 	/* First, check frame starting conditions */
@@ -1290,35 +1467,61 @@ row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot)
 		{
 			/* rows before current row are out of frame */
 			if (pos < winstate->currentpos)
-				return false;
+				return -1;
 		}
-		else if (frameOptions & FRAMEOPTION_RANGE)
+		else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
 		{
 			/* preceding row that is not peer is out of frame */
 			if (pos < winstate->currentpos &&
 				!are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
-				return false;
+				return -1;
 		}
 		else
 			Assert(false);
 	}
 	else if (frameOptions & FRAMEOPTION_START_VALUE)
 	{
+		int64		offset = DatumGetInt64(winstate->startOffsetValue);
+
 		if (frameOptions & FRAMEOPTION_ROWS)
 		{
-			int64		offset = DatumGetInt64(winstate->startOffsetValue);
-
 			/* rows before current row + offset are out of frame */
 			if (frameOptions & FRAMEOPTION_START_VALUE_PRECEDING)
 				offset = -offset;
-
 			if (pos < winstate->currentpos + offset)
-				return false;
+				return 0;
 		}
 		else if (frameOptions & FRAMEOPTION_RANGE)
 		{
-			/* parser should have rejected this */
-			elog(ERROR, "window frame with value offset is not implemented");
+			bool		isnull,
+						preceding,
+						inrange;
+			Datum		slotval,
+						currval;
+
+			slotval = slot_getattr(slot, 1, &isnull);
+			if (isnull)
+				return 0;
+			currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, 1, &isnull);
+			if (isnull)
+				return 0;
+			preceding = (frameOptions & FRAMEOPTION_START_VALUE_PRECEDING) != 0 ? true : false;
+			inrange = DatumGetBool(DirectFunctionCall5(winstate->startRangeFunc,
+													  currval,
+													  slotval,
+													  winstate->startOffsetValue,
+													  BoolGetDatum(preceding),
+													  BoolGetDatum(false)));
+			if (!inrange)
+				return 0;
+		}
+		else if (frameOptions & FRAMEOPTION_GROUPS)
+		{
+			bool		preceding;
+
+			preceding = (frameOptions & FRAMEOPTION_START_VALUE_PRECEDING) != 0 ? true : false;
+			if (!row_is_in_group(winstate, winstate->currentpos, pos, offset, preceding, false))
+				return 0;
 		}
 		else
 			Assert(false);
@@ -1331,42 +1534,84 @@ row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot)
 		{
 			/* rows after current row are out of frame */
 			if (pos > winstate->currentpos)
-				return false;
+				return -1;
 		}
-		else if (frameOptions & FRAMEOPTION_RANGE)
+		else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
 		{
 			/* following row that is not peer is out of frame */
 			if (pos > winstate->currentpos &&
 				!are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
-				return false;
+				return -1;
 		}
 		else
 			Assert(false);
 	}
 	else if (frameOptions & FRAMEOPTION_END_VALUE)
 	{
+		int64		offset = DatumGetInt64(winstate->endOffsetValue);
 		if (frameOptions & FRAMEOPTION_ROWS)
 		{
-			int64		offset = DatumGetInt64(winstate->endOffsetValue);
-
 			/* rows after current row + offset are out of frame */
 			if (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING)
 				offset = -offset;
-
 			if (pos > winstate->currentpos + offset)
-				return false;
+				return -1;
 		}
 		else if (frameOptions & FRAMEOPTION_RANGE)
 		{
-			/* parser should have rejected this */
-			elog(ERROR, "window frame with value offset is not implemented");
+			bool		isnull,
+						preceding,
+						inrange;
+			Datum		slotval,
+						currval;
+
+			slotval = slot_getattr(slot, 1, &isnull);
+			if (isnull)
+				return -1;
+			currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, 1, &isnull);
+			if (isnull)
+				return -1;
+			preceding = (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING) != 0 ? true : false;
+			inrange = DatumGetBool(DirectFunctionCall5(winstate->endRangeFunc,
+													  currval,
+													  slotval,
+													  winstate->endOffsetValue,
+													  BoolGetDatum(preceding),
+													  BoolGetDatum(true)));
+			if (!inrange)
+				return -1;
+		}
+		else if (frameOptions & FRAMEOPTION_GROUPS)
+		{
+			bool		preceding;
+
+			preceding = (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING) != 0 ? true : false;
+			if (!row_is_in_group(winstate, winstate->currentpos, pos, offset, preceding, true))
+				return -1;
 		}
 		else
 			Assert(false);
 	}
 
+	/* Check exclusion clause */
+	if (frameOptions & FRAMEOPTION_EXCLUDE_CURRENT)
+	{
+		if (pos == winstate->currentpos)
+			return 0;
+	} else if (frameOptions & FRAMEOPTION_EXCLUDE_GROUP)
+	{
+		if (are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
+			return 0;
+	}
+	else if (frameOptions & FRAMEOPTION_EXCLUDE_TIES)
+	{
+		if ((pos != winstate->currentpos) &&
+			are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
+			return 0;
+	}
+
 	/* If we get here, it's in frame */
-	return true;
+	return 1;
 }
 
 /*
@@ -1402,7 +1647,7 @@ update_frameheadpos(WindowObject winobj, TupleTableSlot *slot)
 			winstate->frameheadpos = winstate->currentpos;
 			winstate->framehead_valid = true;
 		}
-		else if (frameOptions & FRAMEOPTION_RANGE)
+		else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
 		{
 			int64		fhprev;
 
@@ -1441,11 +1686,11 @@ update_frameheadpos(WindowObject winobj, TupleTableSlot *slot)
 	}
 	else if (frameOptions & FRAMEOPTION_START_VALUE)
 	{
+		int64		offset = DatumGetInt64(winstate->startOffsetValue);
+
 		if (frameOptions & FRAMEOPTION_ROWS)
 		{
 			/* In ROWS mode, bound is physically n before/after current */
-			int64		offset = DatumGetInt64(winstate->startOffsetValue);
-
 			if (frameOptions & FRAMEOPTION_START_VALUE_PRECEDING)
 				offset = -offset;
 
@@ -1464,8 +1709,43 @@ update_frameheadpos(WindowObject winobj, TupleTableSlot *slot)
 		}
 		else if (frameOptions & FRAMEOPTION_RANGE)
 		{
-			/* parser should have rejected this */
-			elog(ERROR, "window frame with value offset is not implemented");
+			bool	isnull,
+					inrange;
+			Datum	slotval,
+					currval;
+			window_gettupleslot(winobj, winstate->frameheadpos, slot);
+			while (winstate->frameheadGroupsCount < winstate->winGroupsCount - 1)
+			{
+				slotval = slot_getattr(slot, 1, &isnull);
+				if (isnull)
+					break;
+				currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, 1, &isnull);
+				if (isnull)
+					break;
+				inrange = DatumGetBool(DirectFunctionCall5(winstate->startRangeFunc,
+													  currval,
+													  slotval,
+													  winstate->startOffsetValue,
+													  BoolGetDatum((frameOptions & FRAMEOPTION_START_VALUE_PRECEDING) ? true : false),
+													  BoolGetDatum(false)));
+				if (inrange)
+					break;
+				winstate->frameheadpos += *(winstate->frameheadWinGroup);
+				winstate->frameheadWinGroup++;
+				winstate->frameheadGroupsCount++;
+				window_gettupleslot(winobj, winstate->frameheadpos, slot);
+			}
+			winstate->framehead_valid = true;
+		}
+		else if (frameOptions & FRAMEOPTION_GROUPS)
+		{
+			if (!row_is_in_group(winstate, winstate->currentpos, winstate->frameheadpos, offset,
+				(frameOptions & FRAMEOPTION_START_VALUE_PRECEDING) ? true : false, false))
+			{
+				winstate->frameheadpos += *(winstate->frameheadWinGroup);
+				winstate->frameheadWinGroup++;
+			}
+			winstate->framehead_valid = true;
 		}
 		else
 			Assert(false);
@@ -1508,7 +1788,7 @@ update_frametailpos(WindowObject winobj, TupleTableSlot *slot)
 			winstate->frametailpos = winstate->currentpos;
 			winstate->frametail_valid = true;
 		}
-		else if (frameOptions & FRAMEOPTION_RANGE)
+		else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
 		{
 			int64		ftnext;
 
@@ -1545,11 +1825,11 @@ update_frametailpos(WindowObject winobj, TupleTableSlot *slot)
 	}
 	else if (frameOptions & FRAMEOPTION_END_VALUE)
 	{
+		int64		offset = DatumGetInt64(winstate->endOffsetValue);
+
 		if (frameOptions & FRAMEOPTION_ROWS)
 		{
 			/* In ROWS mode, bound is physically n before/after current */
-			int64		offset = DatumGetInt64(winstate->endOffsetValue);
-
 			if (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING)
 				offset = -offset;
 
@@ -1568,8 +1848,60 @@ update_frametailpos(WindowObject winobj, TupleTableSlot *slot)
 		}
 		else if (frameOptions & FRAMEOPTION_RANGE)
 		{
-			/* parser should have rejected this */
-			elog(ERROR, "window frame with value offset is not implemented");
+			bool isnull,
+				 inrange;
+			Datum	slotval,
+					currval;
+
+			if (winstate->frametailpos < 0)
+				winstate->frametailpos = 0;
+			if (winstate->frametailpos > 0)
+				winstate->frametailpos -= *(winstate->frametailWinGroup) - 1;
+			while (winstate->frametailGroupsCount < winstate->winGroupsCount - 1)
+			{
+				window_gettupleslot(winobj, winstate->frametailpos + *(winstate->frametailWinGroup), slot);
+				slotval = slot_getattr(slot, 1, &isnull);
+				if (isnull)
+					break;
+				currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, 1, &isnull);
+				if (isnull)
+					break;
+				inrange = DatumGetBool(DirectFunctionCall5(winstate->endRangeFunc,
+													  currval,
+													  slotval,
+													  winstate->endOffsetValue,
+													  BoolGetDatum((frameOptions & FRAMEOPTION_END_VALUE_PRECEDING) ? true : false),
+													  BoolGetDatum(true)));
+				if (!inrange)
+					break;
+				winstate->frametailpos += *(winstate->frametailWinGroup);
+				winstate->frametailWinGroup++;
+				winstate->frametailGroupsCount++;
+				window_gettupleslot(winobj, winstate->frametailpos, slot);
+			}
+
+			winstate->frametailpos += *(winstate->frametailWinGroup) - 1;
+			winstate->frametail_valid = true;
+		}
+		else if (frameOptions & FRAMEOPTION_GROUPS)
+		{
+			if (winstate->frametailpos < 0)
+				winstate->frametailpos = 0;
+
+			if (winstate->frametailpos > 0)
+				winstate->frametailpos -= *(winstate->frametailWinGroup) - 1;
+			if (winstate->prevRowTotal + *(winstate->currWinGroup) <= winstate->currentpos &&
+				winstate->frametailGroupsCount < winstate->winGroupsCount - 1)
+			{
+				winstate->frametailpos += *(winstate->frametailWinGroup);
+				winstate->frametailWinGroup++;
+				winstate->frametailGroupsCount++;
+				winstate->prevRowTotal += *(winstate->currWinGroup);
+				winstate->currWinGroup++;
+			}
+
+			winstate->frametailpos += *(winstate->frametailWinGroup) - 1;
+			winstate->frametail_valid = true;
 		}
 		else
 			Assert(false);
@@ -1627,7 +1959,7 @@ ExecWindowAgg(PlanState *pstate)
 			get_typlenbyval(exprType((Node *) winstate->startOffset->expr),
 							&len, &byval);
 			winstate->startOffsetValue = datumCopy(value, byval, len);
-			if (frameOptions & FRAMEOPTION_ROWS)
+			if (frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS))
 			{
 				/* value is known to be int8 */
 				int64		offset = DatumGetInt64(value);
@@ -1652,7 +1984,7 @@ ExecWindowAgg(PlanState *pstate)
 			get_typlenbyval(exprType((Node *) winstate->endOffset->expr),
 							&len, &byval);
 			winstate->endOffsetValue = datumCopy(value, byval, len);
-			if (frameOptions & FRAMEOPTION_ROWS)
+			if (frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS))
 			{
 				/* value is known to be int8 */
 				int64		offset = DatumGetInt64(value);
@@ -1670,6 +2002,7 @@ ExecWindowAgg(PlanState *pstate)
 	{
 		/* Initialize for first partition and set current row = 0 */
 		begin_partition(winstate);
+
 		/* If there are no input rows, we'll detect that and exit below */
 	}
 	else
@@ -1782,6 +2115,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 				numaggs,
 				aggno;
 	ListCell   *l;
+	FmgrInfo finfo;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1990,6 +2324,17 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 		winstate->agg_winobj = agg_winobj;
 	}
 
+	if (node->startRangeOid != 0)
+	{
+		fmgr_info(node->startRangeOid, &finfo);
+		winstate->startRangeFunc = finfo.fn_addr;
+	}
+	if (node->endRangeOid != 0)
+	{
+		fmgr_info(node->endRangeOid, &finfo);
+		winstate->endRangeFunc = finfo.fn_addr;
+	}
+
 	/* copy frame options to state node for easy access */
 	winstate->frameOptions = node->frameOptions;
 
@@ -2736,7 +3081,7 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	bool		gottuple;
+	int		gottuple;
 	int64		abs_pos;
 
 	Assert(WindowObjectIsValid(winobj));
@@ -2767,7 +3112,7 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	if (gottuple)
 		gottuple = row_is_in_frame(winstate, abs_pos, slot);
 
-	if (!gottuple)
+	if (gottuple != 1)
 	{
 		if (isout)
 			*isout = true;
@@ -2794,7 +3139,7 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 			 * was its mark.  Perhaps use a separate mark for frame head
 			 * probes?
 			 */
-			if ((frameOptions & FRAMEOPTION_RANGE) &&
+			if ((frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS)) &&
 				!(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING))
 			{
 				update_frameheadpos(winobj, winstate->temp_slot_2);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index fd3001c493..977a39a54e 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1009,6 +1009,8 @@ _copyWindowAgg(const WindowAgg *from)
 		COPY_POINTER_FIELD(ordColIdx, from->ordNumCols * sizeof(AttrNumber));
 		COPY_POINTER_FIELD(ordOperators, from->ordNumCols * sizeof(Oid));
 	}
+	COPY_SCALAR_FIELD(startRangeOid);
+	COPY_SCALAR_FIELD(endRangeOid);
 	COPY_SCALAR_FIELD(frameOptions);
 	COPY_NODE_FIELD(startOffset);
 	COPY_NODE_FIELD(endOffset);
@@ -2409,6 +2411,8 @@ _copyWindowClause(const WindowClause *from)
 	COPY_STRING_FIELD(refname);
 	COPY_NODE_FIELD(partitionClause);
 	COPY_NODE_FIELD(orderClause);
+	COPY_SCALAR_FIELD(startRangeOid);
+	COPY_SCALAR_FIELD(endRangeOid);
 	COPY_SCALAR_FIELD(frameOptions);
 	COPY_NODE_FIELD(startOffset);
 	COPY_NODE_FIELD(endOffset);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 7d2aa1a2d3..ef24634320 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2731,6 +2731,8 @@ _equalWindowClause(const WindowClause *a, const WindowClause *b)
 	COMPARE_STRING_FIELD(refname);
 	COMPARE_NODE_FIELD(partitionClause);
 	COMPARE_NODE_FIELD(orderClause);
+	COMPARE_SCALAR_FIELD(startRangeOid);
+	COMPARE_SCALAR_FIELD(endRangeOid);
 	COMPARE_SCALAR_FIELD(frameOptions);
 	COMPARE_NODE_FIELD(startOffset);
 	COMPARE_NODE_FIELD(endOffset);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e0f4befd9f..b1d03a1c9c 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -837,6 +837,8 @@ _outWindowAgg(StringInfo str, const WindowAgg *node)
 	for (i = 0; i < node->ordNumCols; i++)
 		appendStringInfo(str, " %u", node->ordOperators[i]);
 
+	WRITE_OID_FIELD(startRangeOid);
+	WRITE_OID_FIELD(endRangeOid);
 	WRITE_INT_FIELD(frameOptions);
 	WRITE_NODE_FIELD(startOffset);
 	WRITE_NODE_FIELD(endOffset);
@@ -2981,6 +2983,8 @@ _outWindowClause(StringInfo str, const WindowClause *node)
 	WRITE_STRING_FIELD(refname);
 	WRITE_NODE_FIELD(partitionClause);
 	WRITE_NODE_FIELD(orderClause);
+	WRITE_OID_FIELD(startRangeOid);
+	WRITE_OID_FIELD(endRangeOid);
 	WRITE_INT_FIELD(frameOptions);
 	WRITE_NODE_FIELD(startOffset);
 	WRITE_NODE_FIELD(endOffset);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 22d8b9d0d5..3487765376 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -366,6 +366,8 @@ _readWindowClause(void)
 	READ_STRING_FIELD(refname);
 	READ_NODE_FIELD(partitionClause);
 	READ_NODE_FIELD(orderClause);
+	READ_OID_FIELD(startRangeOid);
+	READ_OID_FIELD(endRangeOid);
 	READ_INT_FIELD(frameOptions);
 	READ_NODE_FIELD(startOffset);
 	READ_NODE_FIELD(endOffset);
@@ -2136,6 +2138,8 @@ _readWindowAgg(void)
 	READ_INT_FIELD(ordNumCols);
 	READ_ATTRNUMBER_ARRAY(ordColIdx, local_node->ordNumCols);
 	READ_OID_ARRAY(ordOperators, local_node->ordNumCols);
+	READ_OID_FIELD(startRangeOid);
+	READ_OID_FIELD(endRangeOid);
 	READ_INT_FIELD(frameOptions);
 	READ_NODE_FIELD(startOffset);
 	READ_NODE_FIELD(endOffset);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 86e7e74793..025ecb71e8 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -260,8 +260,8 @@ static Material *make_material(Plan *lefttree);
 static WindowAgg *make_windowagg(List *tlist, Index winref,
 			   int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
 			   int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators,
-			   int frameOptions, Node *startOffset, Node *endOffset,
-			   Plan *lefttree);
+			   Oid startRangeOid, Oid endRangeOid, int frameOptions,
+			   Node *startOffset, Node *endOffset, Plan *lefttree);
 static Group *make_group(List *tlist, List *qual, int numGroupCols,
 		   AttrNumber *grpColIdx, Oid *grpOperators,
 		   Plan *lefttree);
@@ -2117,6 +2117,8 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
 						  ordNumCols,
 						  ordColIdx,
 						  ordOperators,
+						  wc->startRangeOid,
+						  wc->endRangeOid,
 						  wc->frameOptions,
 						  wc->startOffset,
 						  wc->endOffset,
@@ -6076,8 +6078,8 @@ static WindowAgg *
 make_windowagg(List *tlist, Index winref,
 			   int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
 			   int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators,
-			   int frameOptions, Node *startOffset, Node *endOffset,
-			   Plan *lefttree)
+			   Oid startRangeOid, Oid endRangeOid, int frameOptions,
+			   Node *startOffset, Node *endOffset, Plan *lefttree)
 {
 	WindowAgg  *node = makeNode(WindowAgg);
 	Plan	   *plan = &node->plan;
@@ -6089,6 +6091,8 @@ make_windowagg(List *tlist, Index winref,
 	node->ordNumCols = ordNumCols;
 	node->ordColIdx = ordColIdx;
 	node->ordOperators = ordOperators;
+	node->startRangeOid = startRangeOid;
+	node->endRangeOid = endRangeOid;
 	node->frameOptions = frameOptions;
 	node->startOffset = startOffset;
 	node->endOffset = endOffset;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5329432f25..7b405cb05c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -569,7 +569,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	filter_clause
 %type <list>	window_clause window_definition_list opt_partition_clause
 %type <windef>	window_definition over_clause window_specification
-				opt_frame_clause frame_extent frame_bound
+				opt_frame_clause frame_extent frame_bound opt_window_exclusion_clause
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 %type <ival>	generated_when override_kind
@@ -632,7 +632,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR
 	FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
 
-	GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING
+	GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS
 
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
@@ -656,7 +656,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	NULLS_P NUMERIC
 
 	OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OPTIONS OR
-	ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
+	ORDER ORDINALITY OTHERS OUT_P OUTER_P OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
 	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
@@ -676,7 +676,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SUBSCRIPTION SUBSTRING SYMMETRIC SYSID SYSTEM_P
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
-	TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM TREAT TRIGGER TRIM TRUE_P
+	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM TREAT TRIGGER TRIM TRUE_P
 	TRUNCATE TRUSTED TYPE_P TYPES_P
 
 	UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED
@@ -725,8 +725,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * various unreserved keywords as needed to resolve ambiguities (this can't
  * have any bad effects since obviously the keywords will still behave the
  * same as if they weren't keywords).  We need to do this for PARTITION,
- * RANGE, ROWS to support opt_existing_window_name; and for RANGE, ROWS
- * so that they can follow a_expr without creating postfix-operator problems;
+ * RANGE, ROWS, or GROUPS to support opt_existing_window_name; and for RANGE, ROWS
+ * or GROUPS so that they can follow a_expr without creating postfix-operator problems;
  * for GENERATED so that it can follow b_expr;
  * and for NULL so that it can follow b_expr in ColQualList without creating
  * postfix-operator problems.
@@ -746,7 +746,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * blame any funny behavior of UNBOUNDED on the SQL standard, though.
  */
 %nonassoc	UNBOUNDED		/* ideally should have same precedence as IDENT */
-%nonassoc	IDENT GENERATED NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING CUBE ROLLUP
+%nonassoc	IDENT GENERATED NULL_P PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
 %left		Op OPERATOR		/* multi-character ops and user-defined operators */
 %left		'+' '-'
 %left		'*' '/' '%'
@@ -14003,7 +14003,7 @@ window_specification: '(' opt_existing_window_name opt_partition_clause
 		;
 
 /*
- * If we see PARTITION, RANGE, or ROWS as the first token after the '('
+ * If we see PARTITION, RANGE, ROWS or GROUPS as the first token after the '('
  * of a window_specification, we want the assumption to be that there is
  * no existing_window_name; but those keywords are unreserved and so could
  * be ColIds.  We fix this by making them have the same precedence as IDENT
@@ -14023,33 +14023,43 @@ opt_partition_clause: PARTITION BY expr_list		{ $$ = $3; }
 /*
  * For frame clauses, we return a WindowDef, but only some fields are used:
  * frameOptions, startOffset, and endOffset.
- *
- * This is only a subset of the full SQL:2008 frame_clause grammar.
- * We don't support <window frame exclusion> yet.
  */
 opt_frame_clause:
-			RANGE frame_extent
+			RANGE frame_extent opt_window_exclusion_clause
 				{
 					WindowDef *n = $2;
+					WindowDef *n2 = $3;
 					n->frameOptions |= FRAMEOPTION_NONDEFAULT | FRAMEOPTION_RANGE;
-					if (n->frameOptions & (FRAMEOPTION_START_VALUE_PRECEDING |
-										   FRAMEOPTION_END_VALUE_PRECEDING))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("RANGE PRECEDING is only supported with UNBOUNDED"),
-								 parser_errposition(@1)));
-					if (n->frameOptions & (FRAMEOPTION_START_VALUE_FOLLOWING |
-										   FRAMEOPTION_END_VALUE_FOLLOWING))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("RANGE FOLLOWING is only supported with UNBOUNDED"),
-								 parser_errposition(@1)));
+					if ((n->frameOptions & (FRAMEOPTION_START_VALUE_PRECEDING |
+										   FRAMEOPTION_END_VALUE_PRECEDING)) ||
+						(n->frameOptions & (FRAMEOPTION_START_VALUE_FOLLOWING |
+										   FRAMEOPTION_END_VALUE_FOLLOWING)))
+							n->frameOptions |= FRAMEOPTION_RANGE_BETWEEN;
+					if (n2 != NULL)
+						n->frameOptions |= n2->frameOptions;
 					$$ = n;
 				}
-			| ROWS frame_extent
+			| ROWS frame_extent opt_window_exclusion_clause
 				{
 					WindowDef *n = $2;
+					WindowDef *n2 = $3;
 					n->frameOptions |= FRAMEOPTION_NONDEFAULT | FRAMEOPTION_ROWS;
+					if (n2 != NULL)
+						n->frameOptions |= n2->frameOptions;
+					$$ = n;
+				}
+			| GROUPS frame_extent opt_window_exclusion_clause
+				{
+					WindowDef *n = $2;
+					WindowDef *n2 = $3;
+					n->frameOptions |= FRAMEOPTION_NONDEFAULT | FRAMEOPTION_GROUPS;
+					if ((n->frameOptions & (FRAMEOPTION_START_VALUE_PRECEDING |
+										   FRAMEOPTION_END_VALUE_PRECEDING)) ||
+						(n->frameOptions & (FRAMEOPTION_START_VALUE_FOLLOWING |
+										   FRAMEOPTION_END_VALUE_FOLLOWING)))
+							n->frameOptions |= FRAMEOPTION_GROUPS_BETWEEN;
+					if (n2 != NULL)
+						n->frameOptions |= n2->frameOptions;
 					$$ = n;
 				}
 			| /*EMPTY*/
@@ -14166,6 +14176,34 @@ frame_bound:
 				}
 		;
 
+opt_window_exclusion_clause:
+			EXCLUDE CURRENT_P ROW
+				{
+					WindowDef *n = makeNode(WindowDef);
+					n->frameOptions = FRAMEOPTION_EXCLUDE_CURRENT;
+					$$ = n;
+				}
+			| EXCLUDE GROUP_P
+				{
+					WindowDef *n = makeNode(WindowDef);
+					n->frameOptions = FRAMEOPTION_EXCLUDE_GROUP;
+					$$ = n;
+				}
+			| EXCLUDE TIES
+				{
+					WindowDef *n = makeNode(WindowDef);
+					n->frameOptions = FRAMEOPTION_EXCLUDE_TIES;
+					$$ = n;
+				}
+			| EXCLUDE NO OTHERS
+				{
+					WindowDef *n = makeNode(WindowDef);
+					n->frameOptions = FRAMEOPTION_EXCLUDE_NO_OTHERS;
+					$$ = n;
+				}
+			| /*EMPTY*/								{ $$ = NULL; }
+		;
+
 
 /*
  * Supporting nonterminals for expressions.
@@ -15027,6 +15065,7 @@ unreserved_keyword:
 			| GENERATED
 			| GLOBAL
 			| GRANTED
+			| GROUPS
 			| HANDLER
 			| HEADER_P
 			| HOLD
@@ -15092,6 +15131,7 @@ unreserved_keyword:
 			| OPTION
 			| OPTIONS
 			| ORDINALITY
+			| OTHERS
 			| OVER
 			| OVERRIDING
 			| OWNED
@@ -15182,6 +15222,7 @@ unreserved_keyword:
 			| TEMPLATE
 			| TEMPORARY
 			| TEXT_P
+			| TIES
 			| TRANSACTION
 			| TRANSFORM
 			| TRIGGER
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 6a9f1b0217..747139489a 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -420,6 +420,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in window ROWS");
 
 			break;
+		case EXPR_KIND_WINDOW_FRAME_GROUPS:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in window GROUPS");
+			else
+				err = _("grouping operations are not allowed in window GROUPS");
+
+			break;
 		case EXPR_KIND_SELECT_TARGET:
 			/* okay */
 			break;
@@ -835,6 +842,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_WINDOW_ORDER:
 		case EXPR_KIND_WINDOW_FRAME_RANGE:
 		case EXPR_KIND_WINDOW_FRAME_ROWS:
+		case EXPR_KIND_WINDOW_FRAME_GROUPS:
 			err = _("window functions are not allowed in window definitions");
 			break;
 		case EXPR_KIND_SELECT_TARGET:
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 9fbcfd4fa6..d1f6ec4f2a 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -17,13 +17,17 @@
 
 #include "miscadmin.h"
 
+#include "access/amvalidate.h"
 #include "access/heapam.h"
+#include "access/htup_details.h"
 #include "access/tsmapi.h"
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_amproc.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint_fn.h"
+#include "catalog/pg_opfamily.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "nodes/makefuncs.h"
@@ -45,6 +49,7 @@
 #include "rewrite/rewriteManip.h"
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
 #include "utils/rel.h"
 
 
@@ -96,6 +101,8 @@ static List *addTargetToGroupList(ParseState *pstate, TargetEntry *tle,
 static WindowClause *findWindowClause(List *wclist, const char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 					 Node *clause);
+static void getRangeBetweenFuncOid(ParseState *pstate, WindowDef  *windef,
+					 SortBy *sortby, Node *offset, Oid sortOid, Oid *rangeOid);
 
 
 /*
@@ -2608,6 +2615,29 @@ transformSortClause(ParseState *pstate,
 	return sortlist;
 }
 
+static void
+getRangeBetweenFuncOid(ParseState *pstate, WindowDef  *windef, SortBy *sortby,
+					   Node *offset, Oid sortOid, Oid *rangeOid)
+{
+	CatCList		*proclist;
+	HeapTuple		 proctup;
+	Form_pg_amproc	 procform;
+	Oid				 offsetOid = exprType(offset);
+
+	proclist = SearchSysCacheList4(AMPROCNUM, ObjectIdGetDatum(IN_RANGE_BTREE_FAM_OID),
+								   sortOid, offsetOid,
+								   sortby->sortby_dir == SORTBY_DESC ? 2 : 1);
+	if (proclist->n_members != 1)
+			ereport(ERROR,
+					(errcode(ERRCODE_WINDOWING_ERROR),
+					errmsg("no in_range function for given types"),
+					parser_errposition(pstate, windef->location)));
+	proctup = &proclist->members[0]->tuple;
+	procform = (Form_pg_amproc) GETSTRUCT(proctup);
+	*rangeOid = procform->amproc;
+	ReleaseSysCacheList(proclist);
+}
+
 /*
  * transformWindowDefinitions -
  *		transform window definitions (WindowDef to WindowClause)
@@ -2753,6 +2783,7 @@ transformWindowDefinitions(ParseState *pstate,
 					 parser_errposition(pstate, windef->location)));
 		}
 		wc->frameOptions = windef->frameOptions;
+
 		/* Process frame offset expressions */
 		wc->startOffset = transformFrameOffset(pstate, wc->frameOptions,
 											   windef->startOffset);
@@ -2760,9 +2791,35 @@ transformWindowDefinitions(ParseState *pstate,
 											 windef->endOffset);
 		wc->winref = winref;
 
+		/* The RANGE BETWEEN clause requires exactly one ORDER BY column,
+		 * of either an int or date/time type and start/end values to match by type.
+		 */
+		if (wc->frameOptions & FRAMEOPTION_RANGE_BETWEEN)
+		{
+			SortBy		*sortby;
+			TargetEntry *tle;
+			Oid			sortOid;
+
+			if (list_length(orderClause) != 1)
+				ereport(ERROR,
+						(errcode(ERRCODE_WINDOWING_ERROR),
+						errmsg("RANGE clause requires exactly one ORDER BY column"),
+						parser_errposition(pstate, windef->location)));
+			sortby = (SortBy *) lfirst(list_head(windef->orderClause));
+			tle = findTargetlistEntrySQL99(pstate, sortby->node,
+										   targetlist, EXPR_KIND_WINDOW_ORDER);
+
+			/* Check that the sortOid and start/end offset Oids match */
+			sortOid = exprType((Node *) tle->expr);
+			if (wc->frameOptions & FRAMEOPTION_START_VALUE)
+				getRangeBetweenFuncOid(pstate, windef, sortby, wc->startOffset, sortOid, &wc->startRangeOid);
+
+			if (wc->frameOptions & FRAMEOPTION_END_VALUE)
+				getRangeBetweenFuncOid(pstate, windef, sortby, wc->endOffset, sortOid, &wc->endRangeOid);
+		}
+
 		result = lappend(result, wc);
 	}
-
 	return result;
 }
 
@@ -3515,14 +3572,18 @@ transformFrameOffset(ParseState *pstate, int frameOptions, Node *clause)
 	{
 		/* Transform the raw expression tree */
 		node = transformExpr(pstate, clause, EXPR_KIND_WINDOW_FRAME_RANGE);
+		constructName = "RANGE";
+	}
+	else if (frameOptions & FRAMEOPTION_GROUPS)
+	{
+		/* Transform the raw expression tree */
+		node = transformExpr(pstate, clause, EXPR_KIND_WINDOW_FRAME_GROUPS);
 
 		/*
-		 * this needs a lot of thought to decide how to support in the context
-		 * of Postgres' extensible datatype framework
+		 * Like LIMIT clause, simply coerce to int8
 		 */
-		constructName = "RANGE";
-		/* error was already thrown by gram.y, this is just a backstop */
-		elog(ERROR, "window frame with value offset is not implemented");
+		constructName = "GROUPS";
+		node = coerce_to_specific_type(pstate, node, INT8OID, constructName);
 	}
 	else
 	{
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index b2f5e46e3b..d45926f27f 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1805,6 +1805,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_WINDOW_ORDER:
 		case EXPR_KIND_WINDOW_FRAME_RANGE:
 		case EXPR_KIND_WINDOW_FRAME_ROWS:
+		case EXPR_KIND_WINDOW_FRAME_GROUPS:
 		case EXPR_KIND_SELECT_TARGET:
 		case EXPR_KIND_INSERT_TARGET:
 		case EXPR_KIND_UPDATE_SOURCE:
@@ -3428,6 +3429,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "window RANGE";
 		case EXPR_KIND_WINDOW_FRAME_ROWS:
 			return "window ROWS";
+		case EXPR_KIND_WINDOW_FRAME_GROUPS:
+			return "window GROUPS";
 		case EXPR_KIND_SELECT_TARGET:
 			return "SELECT";
 		case EXPR_KIND_INSERT_TARGET:
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index ffae0f3cf3..4a7bc77c0f 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2227,6 +2227,7 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 			break;
 		case EXPR_KIND_WINDOW_FRAME_RANGE:
 		case EXPR_KIND_WINDOW_FRAME_ROWS:
+		case EXPR_KIND_WINDOW_FRAME_GROUPS:
 			err = _("set-returning functions are not allowed in window definitions");
 			break;
 		case EXPR_KIND_SELECT_TARGET:
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 747ef49789..3958ae5cd8 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -44,6 +44,9 @@
 static int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
 static int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
 static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
+static bool in_range_internal(Datum curr, Datum slot, Datum offset, bool preceding, bool end,
+							   PGFunction miIntervalFunc, PGFunction plIntervalFunc,
+							   PGFunction gtComparatorFunc, PGFunction ltComparatorFunc, bool asc);
 
 
 /* common code for timetypmodin and timetztypmodin */
@@ -2754,3 +2757,89 @@ timetz_izone(PG_FUNCTION_ARGS)
 
 	PG_RETURN_TIMETZADT_P(result);
 }
+
+
+/*----------------------------------------------------------
+ *	In RANGE procedures on dates.
+ *---------------------------------------------------------*/
+
+static bool in_range_internal(Datum curr, Datum slot, Datum offset, bool preceding, bool end,
+							   PGFunction miIntervalFunc, PGFunction plIntervalFunc,
+							   PGFunction gtComparatorFunc, PGFunction ltComparatorFunc, bool asc)
+{
+	Datum		result;
+
+	if (preceding)
+		result = DirectFunctionCall2(asc ? miIntervalFunc : plIntervalFunc, curr, offset);
+	else
+		result = DirectFunctionCall2(asc ? plIntervalFunc : miIntervalFunc, curr, offset);
+
+	if (end)
+	{
+		if (DatumGetBool(DirectFunctionCall2(asc ? gtComparatorFunc : ltComparatorFunc, slot, result)))
+			return false;
+	}
+	else
+	{
+		if (DatumGetBool(DirectFunctionCall2(asc ? ltComparatorFunc : gtComparatorFunc, slot, result)))
+			return false;
+	}
+
+	return true;
+}
+
+Datum
+in_range_timetz_interval_asc(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(in_range_internal(PG_GETARG_DATUM(0), PG_GETARG_DATUM(1), PG_GETARG_DATUM(2),
+									 PG_GETARG_BOOL(3), PG_GETARG_BOOL(4),
+									 timetz_mi_interval, timetz_pl_interval,
+									 timetz_gt, timetz_lt, true));
+}
+
+Datum
+in_range_timetz_interval_desc(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(in_range_internal(PG_GETARG_DATUM(0), PG_GETARG_DATUM(1), PG_GETARG_DATUM(2),
+									 PG_GETARG_BOOL(3), PG_GETARG_BOOL(4),
+									 timetz_mi_interval, timetz_pl_interval,
+									 timetz_gt, timetz_lt, false));
+}
+
+Datum
+in_range_date_interval_asc(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(in_range_internal(PG_GETARG_DATUM(0),
+									 DirectFunctionCall1(date_timestamp, PG_GETARG_DATUM(1)),
+									 PG_GETARG_DATUM(2), PG_GETARG_BOOL(3), PG_GETARG_BOOL(4),
+									 date_mi_interval, date_pl_interval,
+									 timestamp_gt, timestamp_lt, true));
+}
+
+Datum
+in_range_date_interval_desc(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(in_range_internal(PG_GETARG_DATUM(0),
+									 DirectFunctionCall1(date_timestamp, PG_GETARG_DATUM(1)),
+									 PG_GETARG_DATUM(2), PG_GETARG_BOOL(3), PG_GETARG_BOOL(4),
+									 date_mi_interval, date_pl_interval,
+									 timestamp_gt, timestamp_lt, false));
+}
+
+Datum
+in_range_time_interval_asc(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(in_range_internal(DirectFunctionCall1(time_interval, PG_GETARG_DATUM(0)),
+									 DirectFunctionCall1(time_interval, PG_GETARG_DATUM(1)),
+									 PG_GETARG_DATUM(2), PG_GETARG_BOOL(3), PG_GETARG_BOOL(4),
+									 interval_mi, interval_pl, interval_gt, interval_lt, true));
+}
+
+Datum
+in_range_time_interval_desc(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(in_range_internal(DirectFunctionCall1(time_interval, PG_GETARG_DATUM(0)),
+									 DirectFunctionCall1(time_interval, PG_GETARG_DATUM(1)),
+									 PG_GETARG_DATUM(2), PG_GETARG_BOOL(3), PG_GETARG_BOOL(4),
+									 interval_mi, interval_pl, interval_gt, interval_lt, false));
+}
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 7352908365..a9d20dd752 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -307,8 +307,261 @@ int4send(PG_FUNCTION_ARGS)
 }
 
 
+/*----------------------------------------------------------
+ *	In RANGE procedures on 16-bit and 32-bit integers.
+ *---------------------------------------------------------*/
+
+Datum
+in_range_int4_int4_asc(PG_FUNCTION_ARGS)
+{
+	int32		curr = PG_GETARG_INT32(0);
+	int32		slot = PG_GETARG_INT32(1);
+	int32		offset = PG_GETARG_INT32(2);
+	bool		preceding = PG_GETARG_BOOL(3);
+	bool		end = PG_GETARG_BOOL(4);
+	int32		result;
+
+	if (offset < 0)
+		ereport(ERROR,
+					(errcode(ERRCODE_WINDOWING_ERROR),
+					errmsg("RANGE offsets cannot be negative. invalid value %d", offset)));
+	if (preceding)
+		offset = -offset;
+
+	if (unlikely(pg_add_s32_overflow(curr, offset, &result)))
+		PG_RETURN_BOOL(true);
+
+	if (end)
+	{
+		if (slot > result)
+			PG_RETURN_BOOL(false);
+	}
+	else
+	{
+		if (slot < result)
+			PG_RETURN_BOOL(false);
+	}
+
+	PG_RETURN_BOOL(true);
+}
+
+Datum
+in_range_int4_int4_desc(PG_FUNCTION_ARGS)
+{
+	int32		curr = PG_GETARG_INT32(0);
+	int32		slot = PG_GETARG_INT32(1);
+	int32		offset = PG_GETARG_INT32(2);
+	bool		preceding = PG_GETARG_BOOL(3);
+	bool		end = PG_GETARG_BOOL(4);
+	int32		result;
+
+	if (offset < 0)
+		ereport(ERROR,
+					(errcode(ERRCODE_WINDOWING_ERROR),
+					errmsg("RANGE offsets cannot be negative. invalid value %d", offset)));
+	if (!preceding)
+		offset = -offset;
+
+	if (unlikely(pg_add_s32_overflow(curr, offset, &result)))
+		PG_RETURN_BOOL(true);
+
+	if (end)
+	{
+		if (slot < result)
+			PG_RETURN_BOOL(false);
+	}
+	else
+	{
+		if (slot > result)
+			PG_RETURN_BOOL(false);
+	}
+
+	PG_RETURN_BOOL(true);
+}
+
+Datum
+in_range_int4_int8_asc(PG_FUNCTION_ARGS)
+{
+	Datum		offset32;
+	int64		offset64 = PG_GETARG_INT64(2);
+
+	offset32 = DirectFunctionCall1(int84, offset64);
+	PG_RETURN_BOOL(DirectFunctionCall5(in_range_int4_int4_asc, PG_GETARG_DATUM(0),
+									   PG_GETARG_DATUM(1),
+									   offset32,
+									   PG_GETARG_DATUM(3),
+									   PG_GETARG_DATUM(4)));
+}
+
+Datum
+in_range_int4_int8_desc(PG_FUNCTION_ARGS)
+{
+	Datum		offset32;
+	int32		offset64 = PG_GETARG_INT64(2);
+
+	offset32 = DirectFunctionCall1(int84, offset64);
+	PG_RETURN_BOOL(DirectFunctionCall5(in_range_int4_int4_desc, PG_GETARG_DATUM(0),
+									   PG_GETARG_DATUM(1),
+									   offset32,
+									   PG_GETARG_DATUM(3),
+									   PG_GETARG_DATUM(4)));
+}
+
+Datum
+in_range_int4_int2_asc(PG_FUNCTION_ARGS)
+{
+	Datum		offset32;
+	int16		offset16 = PG_GETARG_INT16(2);
+
+	offset32 = DirectFunctionCall1(i2toi4, offset16);
+	PG_RETURN_BOOL(DirectFunctionCall5(in_range_int4_int4_asc, PG_GETARG_DATUM(0),
+									   PG_GETARG_DATUM(1),
+									   offset32,
+									   PG_GETARG_DATUM(3),
+									   PG_GETARG_DATUM(4)));
+}
+
+Datum
+in_range_int4_int2_desc(PG_FUNCTION_ARGS)
+{
+	Datum		offset32;
+	int16		offset16 = PG_GETARG_INT16(2);
+
+	offset32 = DirectFunctionCall1(i2toi4, offset16);
+	PG_RETURN_BOOL(DirectFunctionCall5(in_range_int4_int4_desc, PG_GETARG_DATUM(0),
+									   PG_GETARG_DATUM(1),
+									   offset32,
+									   PG_GETARG_DATUM(3),
+									   PG_GETARG_DATUM(4)));
+}
+
+Datum
+in_range_int2_int2_asc(PG_FUNCTION_ARGS)
+{
+	int16		curr = PG_GETARG_INT16(0);
+	int16		slot = PG_GETARG_INT16(1);
+	int16		offset = PG_GETARG_INT16(2);
+	bool		preceding = PG_GETARG_BOOL(3);
+	bool		end = PG_GETARG_BOOL(4);
+	int16		result;
+
+	if (offset < 0)
+		ereport(ERROR,
+					(errcode(ERRCODE_WINDOWING_ERROR),
+					errmsg("RANGE offsets cannot be negative. invalid value %d", offset)));
+	if (preceding)
+		offset = -offset;
+
+	if (unlikely(pg_add_s16_overflow(curr, offset, &result)))
+		PG_RETURN_BOOL(true);
+
+	if (end)
+	{
+		if (slot > result)
+			PG_RETURN_BOOL(false);
+	}
+	else
+	{
+		if (slot < result)
+			PG_RETURN_BOOL(false);
+	}
+
+	PG_RETURN_BOOL(true);
+}
+
+Datum
+in_range_int2_int2_desc(PG_FUNCTION_ARGS)
+{
+	int16		curr = PG_GETARG_INT16(0);
+	int16		slot = PG_GETARG_INT16(1);
+	int16		offset = PG_GETARG_INT16(2);
+	bool		preceding = PG_GETARG_BOOL(3);
+	bool		end = PG_GETARG_BOOL(4);
+	int16		result;
+
+	if (offset < 0)
+		ereport(ERROR,
+					(errcode(ERRCODE_WINDOWING_ERROR),
+					errmsg("RANGE offsets cannot be negative. invalid value %d", offset)));
+	if (!preceding)
+		offset = -offset;
+
+	if (unlikely(pg_add_s16_overflow(curr, offset, &result)))
+		PG_RETURN_BOOL(true);
+
+	if (end)
+	{
+		if (slot < result)
+			PG_RETURN_BOOL(false);
+	}
+	else
+	{
+		if (slot > result)
+			PG_RETURN_BOOL(false);
+	}
+
+	PG_RETURN_BOOL(true);
+}
+
+Datum
+in_range_int2_int8_asc(PG_FUNCTION_ARGS)
+{
+	Datum		offset16;
+	int64		offset64 = PG_GETARG_INT64(2);
+
+	offset16 = DirectFunctionCall1(int82, offset64);
+	PG_RETURN_BOOL(DirectFunctionCall5(in_range_int2_int2_asc, PG_GETARG_DATUM(0),
+									   PG_GETARG_DATUM(1),
+									   offset16,
+									   PG_GETARG_DATUM(3),
+									   PG_GETARG_DATUM(4)));
+}
+
+Datum
+in_range_int2_int8_desc(PG_FUNCTION_ARGS)
+{
+	Datum		offset16;
+	int32		offset64 = PG_GETARG_INT64(2);
+
+	offset16 = DirectFunctionCall1(int82, offset64);
+	PG_RETURN_BOOL(DirectFunctionCall5(in_range_int2_int2_desc, PG_GETARG_DATUM(0),
+									   PG_GETARG_DATUM(1),
+									   offset16,
+									   PG_GETARG_DATUM(3),
+									   PG_GETARG_DATUM(4)));
+}
+
+Datum
+in_range_int2_int4_asc(PG_FUNCTION_ARGS)
+{
+	Datum		offset16;
+	int32		offset32 = PG_GETARG_INT32(2);
+
+	offset16 = DirectFunctionCall1(i4toi2, offset32);
+	PG_RETURN_BOOL(DirectFunctionCall5(in_range_int2_int2_asc, PG_GETARG_DATUM(0),
+									   PG_GETARG_DATUM(1),
+									   offset16,
+									   PG_GETARG_DATUM(3),
+									   PG_GETARG_DATUM(4)));
+}
+
+Datum
+in_range_int2_int4_desc(PG_FUNCTION_ARGS)
+{
+	Datum		offset16;
+	int32		offset32 = PG_GETARG_INT32(2);
+
+	offset16 = DirectFunctionCall1(i4toi2, offset32);
+	PG_RETURN_BOOL(DirectFunctionCall5(in_range_int2_int2_desc, PG_GETARG_DATUM(0),
+									   PG_GETARG_DATUM(1),
+									   offset16,
+									   PG_GETARG_DATUM(3),
+									   PG_GETARG_DATUM(4)));
+}
+
+
 /*
- *		===================
+ *		===================in_range_
  *		CONVERSION ROUTINES
  *		===================
  */
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index ae6a4683d4..a72047edb9 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1101,6 +1101,136 @@ int8shr(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(arg1 >> arg2);
 }
 
+
+/*----------------------------------------------------------
+ *	In RANGE procedures on 64-bit integers.
+ *---------------------------------------------------------*/
+
+Datum
+in_range_int8_int8_asc(PG_FUNCTION_ARGS)
+{
+	int64		curr = PG_GETARG_INT64(0);
+	int64		slot = PG_GETARG_INT64(1);
+	int64		offset = PG_GETARG_INT64(2);
+	bool		preceding = PG_GETARG_BOOL(3);
+	bool		end = PG_GETARG_BOOL(4);
+	int64		result;
+
+	if (offset < 0)
+		ereport(ERROR,
+					(errcode(ERRCODE_WINDOWING_ERROR),
+					errmsg("RANGE offsets cannot be negative. invalid value %zd", offset)));
+	if (preceding)
+		offset = -offset;
+
+	if (unlikely(pg_add_s64_overflow(curr, offset, &result)))
+		PG_RETURN_BOOL(true);
+
+	if (end)
+	{
+		if (slot > result)
+			PG_RETURN_BOOL(false);
+	}
+	else
+	{
+		if (slot < result)
+			PG_RETURN_BOOL(false);
+	}
+
+	PG_RETURN_BOOL(true);
+}
+
+Datum
+in_range_int8_int8_desc(PG_FUNCTION_ARGS)
+{
+	int64		curr = PG_GETARG_INT64(0);
+	int64		slot = PG_GETARG_INT64(1);
+	int64		offset = PG_GETARG_INT64(2);
+	bool		preceding = PG_GETARG_BOOL(3);
+	bool		end = PG_GETARG_BOOL(4);
+	int64		result;
+
+	if (offset < 0)
+		ereport(ERROR,
+					(errcode(ERRCODE_WINDOWING_ERROR),
+					errmsg("RANGE offsets cannot be negative. invalid value %zd", offset)));
+	if (!preceding)
+		offset = -offset;
+
+	if (unlikely(pg_add_s64_overflow(curr, offset, &result)))
+		PG_RETURN_BOOL(true);
+
+	if (end)
+	{
+		if (slot < result)
+			PG_RETURN_BOOL(false);
+	}
+	else
+	{
+		if (slot > result)
+			PG_RETURN_BOOL(false);
+	}
+
+	PG_RETURN_BOOL(true);
+}
+
+Datum
+in_range_int8_int4_asc(PG_FUNCTION_ARGS)
+{
+	Datum		offset64;
+	int32		offset32 = PG_GETARG_INT32(2);
+
+	offset64 = DirectFunctionCall1(int48, offset32);
+	PG_RETURN_BOOL(DirectFunctionCall5(in_range_int8_int8_asc, PG_GETARG_DATUM(0),
+									   PG_GETARG_DATUM(1),
+									   offset64,
+									   PG_GETARG_DATUM(3),
+									   PG_GETARG_DATUM(4)));
+}
+
+Datum
+in_range_int8_int4_desc(PG_FUNCTION_ARGS)
+{
+	Datum		offset64;
+	int32		offset32 = PG_GETARG_INT32(2);
+
+	offset64 = DirectFunctionCall1(int48, offset32);
+	PG_RETURN_BOOL(DirectFunctionCall5(in_range_int8_int8_desc, PG_GETARG_DATUM(0),
+									   PG_GETARG_DATUM(1),
+									   offset64,
+									   PG_GETARG_DATUM(3),
+									   PG_GETARG_DATUM(4)));
+}
+
+Datum
+in_range_int8_int2_asc(PG_FUNCTION_ARGS)
+{
+	Datum		offset64;
+	int16		offset16 = PG_GETARG_INT16(2);
+
+	offset64 = DirectFunctionCall1(int28, offset16);
+	PG_RETURN_BOOL(DirectFunctionCall5(in_range_int8_int8_asc, PG_GETARG_DATUM(0),
+									   PG_GETARG_DATUM(1),
+									   offset64,
+									   PG_GETARG_DATUM(3),
+									   PG_GETARG_DATUM(4)));
+}
+
+Datum
+in_range_int8_int2_desc(PG_FUNCTION_ARGS)
+{
+	Datum		offset64;
+	int16		offset16 = PG_GETARG_INT16(2);
+
+	offset64 = DirectFunctionCall1(int28, offset16);
+	PG_RETURN_BOOL(DirectFunctionCall5(in_range_int8_int8_desc, PG_GETARG_DATUM(0),
+									   PG_GETARG_DATUM(1),
+									   offset64,
+									   PG_GETARG_DATUM(3),
+									   PG_GETARG_DATUM(4)));
+}
+
+
 /*----------------------------------------------------------
  *	Conversion operators.
  *---------------------------------------------------------*/
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c5f5a1ca3f..3d07a648b4 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5877,6 +5877,8 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
 			appendStringInfoString(buf, "RANGE ");
 		else if (wc->frameOptions & FRAMEOPTION_ROWS)
 			appendStringInfoString(buf, "ROWS ");
+		else if (wc->frameOptions & FRAMEOPTION_GROUPS)
+			appendStringInfoString(buf, "GROUPS ");
 		else
 			Assert(false);
 		if (wc->frameOptions & FRAMEOPTION_BETWEEN)
@@ -5917,6 +5919,14 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
 			else
 				Assert(false);
 		}
+		if (wc->frameOptions & FRAMEOPTION_EXCLUDE_CURRENT)
+			appendStringInfoString(buf, "EXCLUDE CURRENT ROW ");
+		else if (wc->frameOptions & FRAMEOPTION_EXCLUDE_GROUP)
+			appendStringInfoString(buf, "EXCLUDE GROUP ");
+		else if (wc->frameOptions & FRAMEOPTION_EXCLUDE_TIES)
+			appendStringInfoString(buf, "EXCLUDE TIES ");
+		else if (wc->frameOptions & FRAMEOPTION_EXCLUDE_NO_OTHERS)
+			appendStringInfoString(buf, "EXCLUDE NO OTHERS ");
 		/* we will now have a trailing space; remove it */
 		buf->len--;
 	}
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index e6a1eed191..effb305e71 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -74,6 +74,9 @@ static void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
 static void AdjustIntervalForTypmod(Interval *interval, int32 typmod);
 static TimestampTz timestamp2timestamptz(Timestamp timestamp);
 static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static bool in_range_internal(Datum curr, Datum slot, Datum offset, bool preceding, bool end,
+							   PGFunction miIntervalFunc, PGFunction plIntervalFunc,
+							   PGFunction gtComparatorFunc, PGFunction ltComparatorFunc, bool asc);
 
 
 /* common code for timestamptypmodin and timestamptztypmodin */
@@ -5148,6 +5151,90 @@ timestamptz_izone(PG_FUNCTION_ARGS)
 	PG_RETURN_TIMESTAMP(result);
 }
 
+/*----------------------------------------------------------
+ *	In RANGE procedures on timestamps.
+ *---------------------------------------------------------*/
+
+static bool in_range_internal(Datum curr, Datum slot, Datum offset, bool preceding, bool end,
+							   PGFunction miIntervalFunc, PGFunction plIntervalFunc,
+							   PGFunction gtComparatorFunc, PGFunction ltComparatorFunc, bool asc)
+{
+	Datum		result;
+
+	if (preceding)
+		result = DirectFunctionCall2(asc ? miIntervalFunc : plIntervalFunc, curr, offset);
+	else
+		result = DirectFunctionCall2(asc ? plIntervalFunc : miIntervalFunc, curr, offset);
+
+	if (end)
+	{
+		if (DatumGetBool(DirectFunctionCall2(asc ? gtComparatorFunc : ltComparatorFunc, slot, result)))
+			return false;
+	}
+	else
+	{
+		if (DatumGetBool(DirectFunctionCall2(asc ? ltComparatorFunc : gtComparatorFunc, slot, result)))
+			return false;
+	}
+
+	return true;
+}
+
+Datum
+in_range_timestamp_interval_asc(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(in_range_internal(PG_GETARG_DATUM(0), PG_GETARG_DATUM(1), PG_GETARG_DATUM(2),
+									 PG_GETARG_BOOL(3), PG_GETARG_BOOL(4),
+									 timestamp_mi_interval, timestamp_pl_interval,
+									 timestamp_gt, timestamp_lt, true));
+}
+
+Datum
+in_range_timestamp_interval_desc(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(in_range_internal(PG_GETARG_DATUM(0), PG_GETARG_DATUM(1), PG_GETARG_DATUM(2),
+									 PG_GETARG_BOOL(3), PG_GETARG_BOOL(4),
+									 timestamp_mi_interval, timestamp_pl_interval,
+									 timestamp_gt, timestamp_lt, false));
+}
+
+Datum
+in_range_timestamptz_interval_asc(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(in_range_internal(PG_GETARG_DATUM(0), PG_GETARG_DATUM(1), PG_GETARG_DATUM(2),
+									 PG_GETARG_BOOL(3), PG_GETARG_BOOL(4),
+									 timestamptz_mi_interval, timestamptz_pl_interval,
+									 timestamp_gt, timestamp_lt, true));
+}
+
+Datum
+in_range_timestamptz_interval_desc(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(in_range_internal(PG_GETARG_DATUM(0), PG_GETARG_DATUM(1), PG_GETARG_DATUM(2),
+									 PG_GETARG_BOOL(3), PG_GETARG_BOOL(4),
+									 timestamptz_mi_interval, timestamptz_pl_interval,
+									 timestamp_gt, timestamp_lt, false));
+}
+
+Datum
+in_range_interval_interval_asc(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(in_range_internal(PG_GETARG_DATUM(0), PG_GETARG_DATUM(1), PG_GETARG_DATUM(2),
+									   PG_GETARG_BOOL(3), PG_GETARG_BOOL(4),
+									   interval_mi, interval_pl,
+									   interval_gt, interval_lt, true));
+}
+
+Datum
+in_range_interval_interval_desc(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(in_range_internal(PG_GETARG_DATUM(0), PG_GETARG_DATUM(1), PG_GETARG_DATUM(2),
+									   PG_GETARG_BOOL(3), PG_GETARG_BOOL(4),
+									   interval_mi, interval_pl,
+									   interval_gt, interval_lt, false));
+}
+
+
 /* generate_series_timestamp()
  * Generate the set of timestamps from start to finish by step
  */
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index f1765af4ba..9b98c04124 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201712251
+#define CATALOG_VERSION_NO	201801311
 
 #endif
diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h
index f545a0580d..751973de03 100644
--- a/src/include/catalog/pg_amproc.h
+++ b/src/include/catalog/pg_amproc.h
@@ -566,5 +566,36 @@ DATA(insert (	4104   603	 603  3  4107 ));
 DATA(insert (	4104   603	 603  4  4108 ));
 DATA(insert (	4104   603	 603  11 4067 ));
 DATA(insert (	4104   603	 603  13  187 ));
+/* in RANGE functions */
+DATA(insert (	5009   20	 20  1 6122 ));
+DATA(insert (	5009   20	 20  2 6123 ));
+DATA(insert (	5009   20	 23  1 6124 ));
+DATA(insert (	5009   20	 23  2 6125 ));
+DATA(insert (	5009   20	 21  1 6126 ));
+DATA(insert (	5009   20	 21  2 6127 ));
+DATA(insert (	5009   23	 20  1 6128 ));
+DATA(insert (	5009   23	 20  2 6129 ));
+DATA(insert (	5009   23	 23  1 6130 ));
+DATA(insert (	5009   23	 23  2 6131 ));
+DATA(insert (	5009   23	 21  1 6132 ));
+DATA(insert (	5009   23	 21  2 6133 ));
+DATA(insert (	5009   21	 20  1 6134 ));
+DATA(insert (	5009   21	 20  2 6135 ));
+DATA(insert (	5009   21	 23  1 6136 ));
+DATA(insert (	5009   21	 23  2 6137 ));
+DATA(insert (	5009   21	 21  1 6138 ));
+DATA(insert (	5009   21	 21  2 6139 ));
+DATA(insert (	5009   1114	 1186  1 6140 ));
+DATA(insert (	5009   1114	 1186  2 6141 ));
+DATA(insert (	5009   1184	 1186  1 6142 ));
+DATA(insert (	5009   1184	 1186  2 6143 ));
+DATA(insert (	5009   1186	 1186  1 6144 ));
+DATA(insert (	5009   1186	 1186  2 6145 ));
+DATA(insert (	5009   1266	 1186  1 6146 ));
+DATA(insert (	5009   1266	 1186  2 6147 ));
+DATA(insert (	5009   1082	 1186  1 6148 ));
+DATA(insert (	5009   1082	 1186  2 6149 ));
+DATA(insert (	5009   1083	 1186  1 6150 ));
+DATA(insert (	5009   1083	 1186  2 6151 ));
 
 #endif							/* PG_AMPROC_H */
diff --git a/src/include/catalog/pg_opfamily.h b/src/include/catalog/pg_opfamily.h
index b544474254..9086fea15f 100644
--- a/src/include/catalog/pg_opfamily.h
+++ b/src/include/catalog/pg_opfamily.h
@@ -187,5 +187,7 @@ DATA(insert OID = 4082 (	3580	pg_lsn_minmax_ops		PGNSP PGUID ));
 DATA(insert OID = 4104 (	3580	box_inclusion_ops		PGNSP PGUID ));
 DATA(insert OID = 5000 (	4000	box_ops		PGNSP PGUID ));
 DATA(insert OID = 5008 (	4000	poly_ops				PGNSP PGUID ));
+DATA(insert OID = 5009 (	403		in_range_ops			PGNSP PGUID ));
+#define IN_RANGE_BTREE_FAM_OID 5009
 
 #endif							/* PG_OPFAMILY_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index f01648c961..b2a172b8f1 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2701,6 +2701,68 @@ DESCR("aggregate final function");
 DATA(insert OID = 3545 (  string_agg				PGNSP PGUID 12 1 0 0 0 t f f f f f i s 2 0 17 "17 17" _null_ _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
 DESCR("concatenate aggregate input into a bytea");
 
+/* In RANGE functions */
+DATA(insert OID = 6122 (  in_range_int8_int8_asc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "20 20 20 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int8_int8_asc _null_ _null_ _null_ ));
+DESCR("in range for int8 column, int8 offset, ascending");
+DATA(insert OID = 6123 (  in_range_int8_int8_desc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "20 20 20 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int8_int8_desc _null_ _null_ _null_ ));
+DESCR("in range for int8 column, int8 offset, descending");
+DATA(insert OID = 6124 (  in_range_int8_int4_asc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "20 20 23 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int8_int4_asc _null_ _null_ _null_ ));
+DESCR("in range for int8 column, int4 offset, ascending");
+DATA(insert OID = 6125 (  in_range_int8_int4_desc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "20 20 23 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int8_int4_desc _null_ _null_ _null_ ));
+DESCR("in range for int8 column, int4 offset, descending");
+DATA(insert OID = 6126 (  in_range_int8_int2_asc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "20 20 21 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int8_int2_asc _null_ _null_ _null_ ));
+DESCR("in range for int8 column, int2 offset, ascending");
+DATA(insert OID = 6127 (  in_range_int8_int2_desc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "20 20 21 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int8_int2_desc _null_ _null_ _null_ ));
+DESCR("in range for int8 column, int2 offset, descending");
+DATA(insert OID = 6128 (  in_range_int4_int8_asc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "23 23 20 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int4_int8_asc _null_ _null_ _null_ ));
+DESCR("in range for int4 column, int8 offset, ascending");
+DATA(insert OID = 6129 (  in_range_int4_int8_desc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "23 23 20 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int4_int8_desc _null_ _null_ _null_ ));
+DESCR("in range for int4 column, int8 offset, descending");
+DATA(insert OID = 6130 (  in_range_int4_int4_asc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "23 23 23 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int4_int4_asc _null_ _null_ _null_ ));
+DESCR("in range for int4 column, int4 offset, ascending");
+DATA(insert OID = 6131 (  in_range_int4_int4_desc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "23 23 23 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int4_int4_desc _null_ _null_ _null_ ));
+DESCR("in range for int4 column, int4 offset, descending");
+DATA(insert OID = 6132 (  in_range_int4_int2_asc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "23 23 21 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int4_int2_asc _null_ _null_ _null_ ));
+DESCR("in range for int4 column, int2 offset, ascending");
+DATA(insert OID = 6133 (  in_range_int4_int2_desc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "23 23 21 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int4_int2_desc _null_ _null_ _null_ ));
+DESCR("in range for int4 column, int2 offset, descending");
+DATA(insert OID = 6134 (  in_range_int2_int8_asc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "21 21 20 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int2_int8_asc _null_ _null_ _null_ ));
+DESCR("in range for int2 column, int8 offset, ascending");
+DATA(insert OID = 6135 (  in_range_int2_int8_desc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "21 21 20 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int2_int8_desc _null_ _null_ _null_ ));
+DESCR("in range for int2 column, int8 offset, descending");
+DATA(insert OID = 6136 (  in_range_int2_int4_asc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "21 21 23 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int2_int4_asc _null_ _null_ _null_ ));
+DESCR("in range for int2 column, int4 offset, ascending");
+DATA(insert OID = 6137 (  in_range_int2_int4_desc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "21 21 23 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int2_int4_desc _null_ _null_ _null_ ));
+DESCR("in range for int2 column, int4 offset, descending");
+DATA(insert OID = 6138 (  in_range_int2_int2_asc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "21 21 21 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int2_int2_asc _null_ _null_ _null_ ));
+DESCR("in range for int2 column, int2 offset, ascending");
+DATA(insert OID = 6139 (  in_range_int2_int2_desc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "21 21 21 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int2_int2_desc _null_ _null_ _null_ ));
+DESCR("in range for int2 column, int2 offset, descending");
+DATA(insert OID = 6140 (  in_range_timestamp_interval_asc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "1114 1114 1186 16 16" _null_ _null_ _null_ _null_ _null_ in_range_timestamp_interval_asc _null_ _null_ _null_ ));
+DESCR("in range for timestamp column, interval offset, ascending");
+DATA(insert OID = 6141 (  in_range_timestamp_interval_desc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "1114 1114 1186 16 16" _null_ _null_ _null_ _null_ _null_ in_range_timestamp_interval_desc _null_ _null_ _null_ ));
+DESCR("in range for timestamp column, interval offset, descending");
+DATA(insert OID = 6142 (  in_range_timestamptz_interval_asc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "1184 1184 1186 16 16" _null_ _null_ _null_ _null_ _null_ in_range_timestamptz_interval_asc _null_ _null_ _null_ ));
+DESCR("in range for timestamptz column, interval offset, ascending");
+DATA(insert OID = 6143 (  in_range_timestamptz_interval_desc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "1184 1184 1186 16 16" _null_ _null_ _null_ _null_ _null_ in_range_timestamptz_interval_desc _null_ _null_ _null_ ));
+DESCR("in range for timestamptz column, interval offset, descending");
+DATA(insert OID = 6144 (  in_range_interval_interval_asc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "1186 1186 1186 16 16" _null_ _null_ _null_ _null_ _null_ in_range_interval_interval_asc _null_ _null_ _null_ ));
+DESCR("in range for interval column, interval offset, ascending");
+DATA(insert OID = 6145 (  in_range_interval_interval_desc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "1186 1186 1186 16 16" _null_ _null_ _null_ _null_ _null_ in_range_interval_interval_desc _null_ _null_ _null_ ));
+DESCR("in range for interval column, interval offset, descending");
+DATA(insert OID = 6146 (  in_range_timetz_interval_asc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "1266 1266 1186 16 16" _null_ _null_ _null_ _null_ _null_ in_range_timetz_interval_asc _null_ _null_ _null_ ));
+DESCR("in range for timetz column, interval offset, ascending");
+DATA(insert OID = 6147 (  in_range_timetz_interval_desc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "1266 1266 1186 16 16" _null_ _null_ _null_ _null_ _null_ in_range_timetz_interval_desc _null_ _null_ _null_ ));
+DESCR("in range for timetz column, interval offset, descending");
+DATA(insert OID = 6148 (  in_range_date_interval_asc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "1082 1082 1186 16 16" _null_ _null_ _null_ _null_ _null_ in_range_date_interval_asc _null_ _null_ _null_ ));
+DESCR("in range for date column, interval offset, ascending");
+DATA(insert OID = 6149 (  in_range_date_interval_desc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "1082 1082 1186 16 16" _null_ _null_ _null_ _null_ _null_ in_range_date_interval_desc _null_ _null_ _null_ ));
+DESCR("in range for date column, interval offset, descending");
+DATA(insert OID = 6150 (  in_range_time_interval_asc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "1083 1083 1186 16 16" _null_ _null_ _null_ _null_ _null_ in_range_time_interval_asc _null_ _null_ _null_ ));
+DESCR("in range for time column, interval offset, ascending");
+DATA(insert OID = 6151 (  in_range_time_interval_desc PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "1083 1083 1186 16 16" _null_ _null_ _null_ _null_ _null_ in_range_time_interval_desc _null_ _null_ _null_ ));
+DESCR("in range for time column, interval offset, descending");
+
 /* To ASCII conversion */
 DATA(insert OID = 1845 ( to_ascii	PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 25 "25" _null_ _null_ _null_ _null_ _null_ to_ascii_default _null_ _null_ _null_ ));
 DESCR("encode text from DB encoding to ASCII text");
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1bf67455e0..1f96de0542 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1893,6 +1893,8 @@ typedef struct WindowAggState
 	int64		aggregatedbase; /* start row for current aggregates */
 	int64		aggregatedupto; /* rows before this one are aggregated */
 
+	PGFunction	startRangeFunc;	/* Start comparator func, used for RANGE with values */
+	PGFunction	endRangeFunc;	/* End comparator func, used for RANGE with values */
 	int			frameOptions;	/* frame_clause options, see WindowDef */
 	ExprState  *startOffset;	/* expression for starting bound offset */
 	ExprState  *endOffset;		/* expression for ending bound offset */
@@ -1922,6 +1924,16 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* used for RANGE BETWEEN and GROUPS BETWEEN with values */
+	int64		winGroupsCount;	/* number of window groups */
+	int64		*winGroupLen;	/* length of each window group */
+	int64		*currWinGroup;	/* current window group */
+	int64		prevRowTotal;	/* previous row total */
+	int64		*frameheadWinGroup;	/* window group for the frame head */
+	int64		*frametailWinGroup;	/* window group for the frame tail */
+	int64		frameheadGroupsCount; /* count of frame head groups */
+	int64		frametailGroupsCount; /* count of frame tail groups */
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 76a73b2a37..ed743e01cc 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -499,22 +499,28 @@ typedef struct WindowDef
  * which were defaulted; the correct behavioral bits must be set either way.
  * The START_foo and END_foo options must come in pairs of adjacent bits for
  * the convenience of gram.y, even though some of them are useless/invalid.
- * We will need more bits (and fields) to cover the full SQL:2008 option set.
  */
 #define FRAMEOPTION_NONDEFAULT					0x00001 /* any specified? */
 #define FRAMEOPTION_RANGE						0x00002 /* RANGE behavior */
 #define FRAMEOPTION_ROWS						0x00004 /* ROWS behavior */
-#define FRAMEOPTION_BETWEEN						0x00008 /* BETWEEN given? */
-#define FRAMEOPTION_START_UNBOUNDED_PRECEDING	0x00010 /* start is U. P. */
-#define FRAMEOPTION_END_UNBOUNDED_PRECEDING		0x00020 /* (disallowed) */
-#define FRAMEOPTION_START_UNBOUNDED_FOLLOWING	0x00040 /* (disallowed) */
-#define FRAMEOPTION_END_UNBOUNDED_FOLLOWING		0x00080 /* end is U. F. */
-#define FRAMEOPTION_START_CURRENT_ROW			0x00100 /* start is C. R. */
-#define FRAMEOPTION_END_CURRENT_ROW				0x00200 /* end is C. R. */
-#define FRAMEOPTION_START_VALUE_PRECEDING		0x00400 /* start is V. P. */
-#define FRAMEOPTION_END_VALUE_PRECEDING			0x00800 /* end is V. P. */
-#define FRAMEOPTION_START_VALUE_FOLLOWING		0x01000 /* start is V. F. */
-#define FRAMEOPTION_END_VALUE_FOLLOWING			0x02000 /* end is V. F. */
+#define FRAMEOPTION_GROUPS						0x00008 /* GROUPS behavior */
+#define FRAMEOPTION_BETWEEN						0x00010 /* BETWEEN given? */
+#define FRAMEOPTION_START_UNBOUNDED_PRECEDING	0x00020 /* start is U. P. */
+#define FRAMEOPTION_END_UNBOUNDED_PRECEDING		0x00040 /* (disallowed) */
+#define FRAMEOPTION_START_UNBOUNDED_FOLLOWING	0x00080 /* (disallowed) */
+#define FRAMEOPTION_END_UNBOUNDED_FOLLOWING		0x00100 /* end is U. F. */
+#define FRAMEOPTION_START_CURRENT_ROW			0x00200 /* start is C. R. */
+#define FRAMEOPTION_END_CURRENT_ROW				0x00400 /* end is C. R. */
+#define FRAMEOPTION_START_VALUE_PRECEDING		0x00800 /* start is V. P. */
+#define FRAMEOPTION_END_VALUE_PRECEDING			0x01000 /* end is V. P. */
+#define FRAMEOPTION_START_VALUE_FOLLOWING		0x02000 /* start is V. F. */
+#define FRAMEOPTION_END_VALUE_FOLLOWING			0x04000 /* end is V. F. */
+#define FRAMEOPTION_RANGE_BETWEEN				0x08000 /* RANGE BETWEEN with values */
+#define FRAMEOPTION_GROUPS_BETWEEN				0x010000 /* GROUPS BETWEEN with values */
+#define FRAMEOPTION_EXCLUDE_CURRENT				0x020000 /* exclude current row */
+#define FRAMEOPTION_EXCLUDE_TIES				0x040000 /* exclude ties */
+#define FRAMEOPTION_EXCLUDE_NO_OTHERS			0x080000 /* exclude no others */
+#define FRAMEOPTION_EXCLUDE_GROUP				0x100000 /* exclude group */
 
 #define FRAMEOPTION_START_VALUE \
 	(FRAMEOPTION_START_VALUE_PRECEDING | FRAMEOPTION_START_VALUE_FOLLOWING)
@@ -525,6 +531,10 @@ typedef struct WindowDef
 	(FRAMEOPTION_RANGE | FRAMEOPTION_START_UNBOUNDED_PRECEDING | \
 	 FRAMEOPTION_END_CURRENT_ROW)
 
+#define FRAMEOPTION_EXCLUSION \
+	(FRAMEOPTION_EXCLUDE_CURRENT | FRAMEOPTION_EXCLUDE_TIES | \
+	 FRAMEOPTION_EXCLUDE_GROUP)
+
 /*
  * RangeSubselect - subquery appearing in a FROM clause
  */
@@ -1275,8 +1285,9 @@ typedef struct GroupingSet
  * if the clause originally came from WINDOW, and is NULL if it originally
  * was an OVER clause (but note that we collapse out duplicate OVERs).
  * partitionClause and orderClause are lists of SortGroupClause structs.
- * winref is an ID number referenced by WindowFunc nodes; it must be unique
- * among the members of a Query's windowClause list.
+ * startRangeFunc and/or endRangeFunc are set if the clause is a RANGE BETWEEN
+ * with values. winref is an ID number referenced by WindowFunc nodes;it must
+ * be unique among the members of a Query's windowClause list.
  * When refname isn't null, the partitionClause is always copied from there;
  * the orderClause might or might not be copied (see copiedOrder); the framing
  * options are never copied, per spec.
@@ -1288,6 +1299,8 @@ typedef struct WindowClause
 	char	   *refname;		/* referenced window name, if any */
 	List	   *partitionClause;	/* PARTITION BY list */
 	List	   *orderClause;	/* ORDER BY list */
+	Oid			startRangeOid;	/* Start comparator Oid, used for RANGE with values */
+	Oid			endRangeOid;	/* End comparator Oid, used for RANGE with values */
 	int			frameOptions;	/* frame_clause options, see WindowDef */
 	Node	   *startOffset;	/* expression for starting bound, if any */
 	Node	   *endOffset;		/* expression for ending bound, if any */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index baf3c07417..ce2cbf1ef7 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -808,6 +808,8 @@ typedef struct WindowAgg
 	int			ordNumCols;		/* number of columns in ordering clause */
 	AttrNumber *ordColIdx;		/* their indexes in the target list */
 	Oid		   *ordOperators;	/* equality operators for ordering columns */
+	Oid			startRangeOid;	/* Start comparator Oid, used for RANGE with values */
+	Oid			endRangeOid;	/* End comparator Oid, used for RANGE with values */
 	int			frameOptions;	/* frame_clause options, see WindowDef */
 	Node	   *startOffset;	/* expression for starting bound, if any */
 	Node	   *endOffset;		/* expression for ending bound, if any */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 26af944e03..cf32197bc3 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -182,6 +182,7 @@ PG_KEYWORD("granted", GRANTED, UNRESERVED_KEYWORD)
 PG_KEYWORD("greatest", GREATEST, COL_NAME_KEYWORD)
 PG_KEYWORD("group", GROUP_P, RESERVED_KEYWORD)
 PG_KEYWORD("grouping", GROUPING, COL_NAME_KEYWORD)
+PG_KEYWORD("groups", GROUPS, UNRESERVED_KEYWORD)
 PG_KEYWORD("handler", HANDLER, UNRESERVED_KEYWORD)
 PG_KEYWORD("having", HAVING, RESERVED_KEYWORD)
 PG_KEYWORD("header", HEADER_P, UNRESERVED_KEYWORD)
@@ -283,6 +284,7 @@ PG_KEYWORD("options", OPTIONS, UNRESERVED_KEYWORD)
 PG_KEYWORD("or", OR, RESERVED_KEYWORD)
 PG_KEYWORD("order", ORDER, RESERVED_KEYWORD)
 PG_KEYWORD("ordinality", ORDINALITY, UNRESERVED_KEYWORD)
+PG_KEYWORD("others", OTHERS, UNRESERVED_KEYWORD)
 PG_KEYWORD("out", OUT_P, COL_NAME_KEYWORD)
 PG_KEYWORD("outer", OUTER_P, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("over", OVER, UNRESERVED_KEYWORD)
@@ -397,6 +399,7 @@ PG_KEYWORD("template", TEMPLATE, UNRESERVED_KEYWORD)
 PG_KEYWORD("temporary", TEMPORARY, UNRESERVED_KEYWORD)
 PG_KEYWORD("text", TEXT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("then", THEN, RESERVED_KEYWORD)
+PG_KEYWORD("ties", TIES, UNRESERVED_KEYWORD)
 PG_KEYWORD("time", TIME, COL_NAME_KEYWORD)
 PG_KEYWORD("timestamp", TIMESTAMP, COL_NAME_KEYWORD)
 PG_KEYWORD("to", TO, RESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 4e96fa7907..1abfe2400f 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -45,6 +45,7 @@ typedef enum ParseExprKind
 	EXPR_KIND_WINDOW_ORDER,		/* window definition ORDER BY */
 	EXPR_KIND_WINDOW_FRAME_RANGE,	/* window frame clause with RANGE */
 	EXPR_KIND_WINDOW_FRAME_ROWS,	/* window frame clause with ROWS */
+	EXPR_KIND_WINDOW_FRAME_GROUPS,	/* window frame clause with GROUPS */
 	EXPR_KIND_SELECT_TARGET,	/* SELECT target list item */
 	EXPR_KIND_INSERT_TARGET,	/* INSERT target list item */
 	EXPR_KIND_UPDATE_SOURCE,	/* UPDATE assignment source item */
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 684f7f20a8..d1928ca860 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -717,6 +717,36 @@ macaddr8_le(macaddr8,macaddr8)
 macaddr8_gt(macaddr8,macaddr8)
 macaddr8_ge(macaddr8,macaddr8)
 macaddr8_ne(macaddr8,macaddr8)
+in_range_int8_int8_asc(bigint,bigint,bigint,boolean,boolean)
+in_range_int8_int8_desc(bigint,bigint,bigint,boolean,boolean)
+in_range_int8_int4_asc(bigint,bigint,integer,boolean,boolean)
+in_range_int8_int4_desc(bigint,bigint,integer,boolean,boolean)
+in_range_int8_int2_asc(bigint,bigint,smallint,boolean,boolean)
+in_range_int8_int2_desc(bigint,bigint,smallint,boolean,boolean)
+in_range_int4_int8_asc(integer,integer,bigint,boolean,boolean)
+in_range_int4_int8_desc(integer,integer,bigint,boolean,boolean)
+in_range_int4_int4_asc(integer,integer,integer,boolean,boolean)
+in_range_int4_int4_desc(integer,integer,integer,boolean,boolean)
+in_range_int4_int2_asc(integer,integer,smallint,boolean,boolean)
+in_range_int4_int2_desc(integer,integer,smallint,boolean,boolean)
+in_range_int2_int8_asc(smallint,smallint,bigint,boolean,boolean)
+in_range_int2_int8_desc(smallint,smallint,bigint,boolean,boolean)
+in_range_int2_int4_asc(smallint,smallint,integer,boolean,boolean)
+in_range_int2_int4_desc(smallint,smallint,integer,boolean,boolean)
+in_range_int2_int2_asc(smallint,smallint,smallint,boolean,boolean)
+in_range_int2_int2_desc(smallint,smallint,smallint,boolean,boolean)
+in_range_timestamp_interval_asc(timestamp without time zone,timestamp without time zone,interval,boolean,boolean)
+in_range_timestamp_interval_desc(timestamp without time zone,timestamp without time zone,interval,boolean,boolean)
+in_range_timestamptz_interval_asc(timestamp with time zone,timestamp with time zone,interval,boolean,boolean)
+in_range_timestamptz_interval_desc(timestamp with time zone,timestamp with time zone,interval,boolean,boolean)
+in_range_interval_interval_asc(interval,interval,interval,boolean,boolean)
+in_range_interval_interval_desc(interval,interval,interval,boolean,boolean)
+in_range_timetz_interval_asc(time with time zone,time with time zone,interval,boolean,boolean)
+in_range_timetz_interval_desc(time with time zone,time with time zone,interval,boolean,boolean)
+in_range_date_interval_asc(date,date,interval,boolean,boolean)
+in_range_date_interval_desc(date,date,interval,boolean,boolean)
+in_range_time_interval_asc(time without time zone,time without time zone,interval,boolean,boolean)
+in_range_time_interval_desc(time without time zone,time without time zone,interval,boolean,boolean)
 -- restore normal output mode
 \a\t
 -- List of functions used by libpq's fe-lobj.c
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 19f909f3d1..e9b76908bc 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -5,19 +5,24 @@ CREATE TEMPORARY TABLE empsalary (
     depname varchar,
     empno bigint,
     salary int,
-    enroll_date date
+    enroll_date date,
+    enroll_time time,
+    enroll_timetz timetz,
+    enroll_interval interval,
+    enroll_timestamptz timestamptz,
+    enroll_timestamp timestamp
 );
 INSERT INTO empsalary VALUES
-('develop', 10, 5200, '2007-08-01'),
-('sales', 1, 5000, '2006-10-01'),
-('personnel', 5, 3500, '2007-12-10'),
-('sales', 4, 4800, '2007-08-08'),
-('personnel', 2, 3900, '2006-12-23'),
-('develop', 7, 4200, '2008-01-01'),
-('develop', 9, 4500, '2008-01-01'),
-('sales', 3, 4800, '2007-08-01'),
-('develop', 8, 6000, '2006-10-01'),
-('develop', 11, 5200, '2007-08-15');
+('develop', 10, 5200, '2007-08-01', '11:00', '11:00 BST', '1 year'::interval, TIMESTAMP '2000-10-19 10:23:54+01', TIMESTAMP '2000-10-19 10:23:54'),
+('sales', 1, 5000, '2006-10-01', '12:00', '12:00 BST', '2 years'::interval, TIMESTAMP '2001-10-19 10:23:54+01', TIMESTAMP '2001-10-19 10:23:54'),
+('personnel', 5, 3500, '2007-12-10', '13:00', '13:00 BST', '3 years'::interval, TIMESTAMP '2001-10-19 10:23:54+01', TIMESTAMP '2001-10-19 10:23:54'),
+('sales', 4, 4800, '2007-08-08', '14:00', '14:00 BST', '4 years'::interval, TIMESTAMP '2002-10-19 10:23:54+01', TIMESTAMP '2002-10-19 10:23:54'),
+('personnel', 2, 3900, '2006-12-23', '15:00', '15:00 BST', '5 years'::interval, TIMESTAMP '2003-10-19 10:23:54+01', TIMESTAMP '2003-10-19 10:23:54'),
+('develop', 7, 4200, '2008-01-01', '15:00', '15:00 BST', '5 years'::interval, TIMESTAMP '2004-10-19 10:23:54+01', TIMESTAMP '2004-10-19 10:23:54'),
+('develop', 9, 4500, '2008-01-01', '17:00', '17:00 BST', '7 years'::interval, TIMESTAMP '2005-10-19 10:23:54+01', TIMESTAMP '2005-10-19 10:23:54'),
+('sales', 3, 4800, '2007-08-01', '18:00', '18:00 BST', '8 years'::interval, TIMESTAMP '2006-10-19 10:23:54+01', TIMESTAMP '2006-10-19 10:23:54'),
+('develop', 8, 6000, '2006-10-01', '19:00', '19:00 BST', '9 years'::interval, TIMESTAMP '2007-10-19 10:23:54+01', TIMESTAMP '2007-10-19 10:23:54'),
+('develop', 11, 5200, '2007-08-15', '20:00', '20:00 BST', '10 years'::interval, TIMESTAMP '2008-10-19 10:23:54+01', TIMESTAMP '2008-10-19 10:23:54');
 SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
   depname  | empno | salary |  sum  
 -----------+-------+--------+-------
@@ -819,6 +824,74 @@ FROM tenk1 WHERE unique1 < 10;
   10 |       0 |    0
 (10 rows)
 
+SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude no others),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+   7 |       4 |    0
+  13 |       2 |    2
+  22 |       1 |    1
+  26 |       6 |    2
+  29 |       9 |    1
+  31 |       8 |    0
+  32 |       5 |    1
+  23 |       3 |    3
+  15 |       7 |    3
+  10 |       0 |    0
+(10 rows)
+
+SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude current row),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+   3 |       4 |    0
+  11 |       2 |    2
+  21 |       1 |    1
+  20 |       6 |    2
+  20 |       9 |    1
+  23 |       8 |    0
+  27 |       5 |    1
+  20 |       3 |    3
+   8 |       7 |    3
+  10 |       0 |    0
+(10 rows)
+
+SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude group),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+     |       4 |    0
+     |       2 |    2
+     |       1 |    1
+     |       6 |    2
+     |       9 |    1
+     |       8 |    0
+     |       5 |    1
+     |       3 |    3
+     |       7 |    3
+     |       0 |    0
+(10 rows)
+
+SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude ties),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+   4 |       4 |    0
+   2 |       2 |    2
+   1 |       1 |    1
+   6 |       6 |    2
+   9 |       9 |    1
+   8 |       8 |    0
+   5 |       5 |    1
+   3 |       3 |    3
+   7 |       7 |    3
+   0 |       0 |    0
+(10 rows)
+
 SELECT sum(unique1) over (rows between 2 preceding and 1 preceding),
 	unique1, four
 FROM tenk1 WHERE unique1 < 10;
@@ -887,13 +960,57 @@ FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
   10 |       7 |    3
 (10 rows)
 
--- fail: not implemented yet
-SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding),
+SELECT sum(unique1) over (w range between unbounded preceding and current row exclude current row),
 	unique1, four
-FROM tenk1 WHERE unique1 < 10;
-ERROR:  RANGE PRECEDING is only supported with UNBOUNDED
-LINE 1: SELECT sum(unique1) over (order by four range between 2::int...
-                                                ^
+FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
+ sum | unique1 | four 
+-----+---------+------
+  12 |       0 |    0
+   4 |       8 |    0
+   8 |       4 |    0
+  22 |       5 |    1
+  18 |       9 |    1
+  26 |       1 |    1
+  29 |       6 |    2
+  33 |       2 |    2
+  42 |       3 |    3
+  38 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (w range between unbounded preceding and current row exclude group),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
+ sum | unique1 | four 
+-----+---------+------
+     |       0 |    0
+     |       8 |    0
+     |       4 |    0
+  12 |       5 |    1
+  12 |       9 |    1
+  12 |       1 |    1
+  27 |       6 |    2
+  27 |       2 |    2
+  35 |       3 |    3
+  35 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (w range between unbounded preceding and current row exclude ties),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
+ sum | unique1 | four 
+-----+---------+------
+   0 |       0 |    0
+   8 |       8 |    0
+   4 |       4 |    0
+  17 |       5 |    1
+  21 |       9 |    1
+  13 |       1 |    1
+  33 |       6 |    2
+  29 |       2 |    2
+  38 |       3 |    3
+  42 |       7 |    3
+(10 rows)
+
 SELECT first_value(unique1) over w,
 	nth_value(unique1, 2) over w AS nth_2,
 	last_value(unique1) over w, unique1, four
@@ -958,6 +1075,1644 @@ SELECT pg_get_viewdef('v_window');
     FROM generate_series(1, 10) i(i);
 (1 row)
 
+CREATE OR REPLACE TEMP VIEW v_window AS
+	SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following
+	exclude current row) as sum_rows FROM generate_series(1, 10) i;
+SELECT * FROM v_window;
+ i  | sum_rows 
+----+----------
+  1 |        2
+  2 |        4
+  3 |        6
+  4 |        8
+  5 |       10
+  6 |       12
+  7 |       14
+  8 |       16
+  9 |       18
+ 10 |        9
+(10 rows)
+
+SELECT pg_get_viewdef('v_window');
+                                              pg_get_viewdef                                               
+-----------------------------------------------------------------------------------------------------------
+  SELECT i.i,                                                                                             +
+     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+    FROM generate_series(1, 10) i(i);
+(1 row)
+
+CREATE OR REPLACE TEMP VIEW v_window AS
+	SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following
+	exclude group) as sum_rows FROM generate_series(1, 10) i;
+SELECT * FROM v_window;
+ i  | sum_rows 
+----+----------
+  1 |        2
+  2 |        4
+  3 |        6
+  4 |        8
+  5 |       10
+  6 |       12
+  7 |       14
+  8 |       16
+  9 |       18
+ 10 |        9
+(10 rows)
+
+SELECT pg_get_viewdef('v_window');
+                                           pg_get_viewdef                                            
+-----------------------------------------------------------------------------------------------------
+  SELECT i.i,                                                                                       +
+     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+    FROM generate_series(1, 10) i(i);
+(1 row)
+
+CREATE OR REPLACE TEMP VIEW v_window AS
+	SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following
+	exclude ties) as sum_rows FROM generate_series(1, 10) i;
+SELECT * FROM v_window;
+ i  | sum_rows 
+----+----------
+  1 |        3
+  2 |        6
+  3 |        9
+  4 |       12
+  5 |       15
+  6 |       18
+  7 |       21
+  8 |       24
+  9 |       27
+ 10 |       19
+(10 rows)
+
+SELECT pg_get_viewdef('v_window');
+                                           pg_get_viewdef                                           
+----------------------------------------------------------------------------------------------------
+  SELECT i.i,                                                                                      +
+     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+    FROM generate_series(1, 10) i(i);
+(1 row)
+
+CREATE OR REPLACE TEMP VIEW v_window AS
+	SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following
+	exclude no others) as sum_rows FROM generate_series(1, 10) i;
+SELECT * FROM v_window;
+ i  | sum_rows 
+----+----------
+  1 |        3
+  2 |        6
+  3 |        9
+  4 |       12
+  5 |       15
+  6 |       18
+  7 |       21
+  8 |       24
+  9 |       27
+ 10 |       19
+(10 rows)
+
+SELECT pg_get_viewdef('v_window');
+                                             pg_get_viewdef                                              
+---------------------------------------------------------------------------------------------------------
+  SELECT i.i,                                                                                           +
+     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE NO OTHERS) AS sum_rows+
+    FROM generate_series(1, 10) i(i);
+(1 row)
+
+CREATE OR REPLACE TEMP VIEW v_window AS
+	SELECT i, sum(i) over (order by i groups between 1 preceding and 1 following
+	exclude no others) as sum_rows FROM generate_series(1, 10) i;
+SELECT * FROM v_window;
+ i  | sum_rows 
+----+----------
+  1 |        3
+  2 |        6
+  3 |        9
+  4 |       12
+  5 |       15
+  6 |       18
+  7 |       21
+  8 |       24
+  9 |       27
+ 10 |       19
+(10 rows)
+
+SELECT pg_get_viewdef('v_window');
+                                              pg_get_viewdef                                               
+-----------------------------------------------------------------------------------------------------------
+  SELECT i.i,                                                                                             +
+     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE NO OTHERS) AS sum_rows+
+    FROM generate_series(1, 10) i(i);
+(1 row)
+
+-- RANGE BETWEEN with values tests
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+     |       0 |    0
+     |       8 |    0
+     |       4 |    0
+  12 |       5 |    1
+  12 |       9 |    1
+  12 |       1 |    1
+  27 |       6 |    2
+  27 |       2 |    2
+  23 |       3 |    3
+  23 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (order by four desc range between 2::int8 preceding and 1::int2 preceding),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+     |       3 |    3
+     |       7 |    3
+  10 |       6 |    2
+  10 |       2 |    2
+  18 |       9 |    1
+  18 |       5 |    1
+  18 |       1 |    1
+  23 |       0 |    0
+  23 |       8 |    0
+  23 |       4 |    0
+(10 rows)
+
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude no others),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+     |       0 |    0
+     |       8 |    0
+     |       4 |    0
+  12 |       5 |    1
+  12 |       9 |    1
+  12 |       1 |    1
+  27 |       6 |    2
+  27 |       2 |    2
+  23 |       3 |    3
+  23 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude current row),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+     |       0 |    0
+     |       8 |    0
+     |       4 |    0
+  12 |       5 |    1
+  12 |       9 |    1
+  12 |       1 |    1
+  27 |       6 |    2
+  27 |       2 |    2
+  23 |       3 |    3
+  23 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude group),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+     |       0 |    0
+     |       8 |    0
+     |       4 |    0
+  12 |       5 |    1
+  12 |       9 |    1
+  12 |       1 |    1
+  27 |       6 |    2
+  27 |       2 |    2
+  23 |       3 |    3
+  23 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude ties),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+     |       0 |    0
+     |       8 |    0
+     |       4 |    0
+  12 |       5 |    1
+  12 |       9 |    1
+  12 |       1 |    1
+  27 |       6 |    2
+  27 |       2 |    2
+  23 |       3 |    3
+  23 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 6::int2 following exclude ties),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+  33 |       0 |    0
+  41 |       8 |    0
+  37 |       4 |    0
+  35 |       5 |    1
+  39 |       9 |    1
+  31 |       1 |    1
+  43 |       6 |    2
+  39 |       2 |    2
+  26 |       3 |    3
+  30 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 6::int2 following exclude group),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+  33 |       0 |    0
+  33 |       8 |    0
+  33 |       4 |    0
+  30 |       5 |    1
+  30 |       9 |    1
+  30 |       1 |    1
+  37 |       6 |    2
+  37 |       2 |    2
+  23 |       3 |    3
+  23 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (partition by four order by unique1 range between 5::int8 preceding and 6::int2 following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+   4 |       0 |    0
+  12 |       4 |    0
+  12 |       8 |    0
+   6 |       1 |    1
+  15 |       5 |    1
+  14 |       9 |    1
+   8 |       2 |    2
+   8 |       6 |    2
+  10 |       3 |    3
+  10 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (partition by four order by unique1 range between 5::int8 preceding and 6::int2 following
+	exclude current row),unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+   4 |       0 |    0
+   8 |       4 |    0
+   4 |       8 |    0
+   5 |       1 |    1
+  10 |       5 |    1
+   5 |       9 |    1
+   6 |       2 |    2
+   2 |       6 |    2
+   7 |       3 |    3
+   3 |       7 |    3
+(10 rows)
+
+select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following),
+	salary, enroll_date from empsalary;
+  sum  | salary | enroll_date 
+-------+--------+-------------
+ 34900 |   5000 | 10-01-2006
+ 34900 |   6000 | 10-01-2006
+ 38400 |   3900 | 12-23-2006
+ 47100 |   4800 | 08-01-2007
+ 47100 |   5200 | 08-01-2007
+ 47100 |   4800 | 08-08-2007
+ 47100 |   5200 | 08-15-2007
+ 36100 |   3500 | 12-10-2007
+ 32200 |   4500 | 01-01-2008
+ 32200 |   4200 | 01-01-2008
+(10 rows)
+
+select sum(salary) over (order by enroll_date desc range between '1 year'::interval preceding and '1 year'::interval following),
+	salary, enroll_date from empsalary;
+  sum  | salary | enroll_date 
+-------+--------+-------------
+ 32200 |   4200 | 01-01-2008
+ 32200 |   4500 | 01-01-2008
+ 36100 |   3500 | 12-10-2007
+ 47100 |   5200 | 08-15-2007
+ 47100 |   4800 | 08-08-2007
+ 47100 |   4800 | 08-01-2007
+ 47100 |   5200 | 08-01-2007
+ 38400 |   3900 | 12-23-2006
+ 34900 |   5000 | 10-01-2006
+ 34900 |   6000 | 10-01-2006
+(10 rows)
+
+select sum(salary) over (order by enroll_date desc range between '1 year'::interval following and '1 year'::interval following),
+	salary, enroll_date from empsalary;
+ sum | salary | enroll_date 
+-----+--------+-------------
+     |   4200 | 01-01-2008
+     |   4500 | 01-01-2008
+     |   3500 | 12-10-2007
+     |   5200 | 08-15-2007
+     |   4800 | 08-08-2007
+     |   4800 | 08-01-2007
+     |   5200 | 08-01-2007
+     |   3900 | 12-23-2006
+     |   5000 | 10-01-2006
+     |   6000 | 10-01-2006
+(10 rows)
+
+select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following
+	exclude current row), salary, enroll_date from empsalary;
+  sum  | salary | enroll_date 
+-------+--------+-------------
+ 29900 |   5000 | 10-01-2006
+ 28900 |   6000 | 10-01-2006
+ 34500 |   3900 | 12-23-2006
+ 42300 |   4800 | 08-01-2007
+ 41900 |   5200 | 08-01-2007
+ 42300 |   4800 | 08-08-2007
+ 41900 |   5200 | 08-15-2007
+ 32600 |   3500 | 12-10-2007
+ 27700 |   4500 | 01-01-2008
+ 28000 |   4200 | 01-01-2008
+(10 rows)
+
+select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following
+	exclude group), salary, enroll_date from empsalary;
+  sum  | salary | enroll_date 
+-------+--------+-------------
+ 23900 |   5000 | 10-01-2006
+ 23900 |   6000 | 10-01-2006
+ 34500 |   3900 | 12-23-2006
+ 37100 |   4800 | 08-01-2007
+ 37100 |   5200 | 08-01-2007
+ 42300 |   4800 | 08-08-2007
+ 41900 |   5200 | 08-15-2007
+ 32600 |   3500 | 12-10-2007
+ 23500 |   4500 | 01-01-2008
+ 23500 |   4200 | 01-01-2008
+(10 rows)
+
+select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following
+	exclude ties), salary, enroll_date from empsalary;
+  sum  | salary | enroll_date 
+-------+--------+-------------
+ 28900 |   5000 | 10-01-2006
+ 29900 |   6000 | 10-01-2006
+ 38400 |   3900 | 12-23-2006
+ 41900 |   4800 | 08-01-2007
+ 42300 |   5200 | 08-01-2007
+ 47100 |   4800 | 08-08-2007
+ 47100 |   5200 | 08-15-2007
+ 36100 |   3500 | 12-10-2007
+ 28000 |   4500 | 01-01-2008
+ 27700 |   4200 | 01-01-2008
+(10 rows)
+
+select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following),
+	salary, enroll_time from empsalary;
+  sum  | salary | enroll_time 
+-------+--------+-------------
+ 13700 |   5200 | 11:00:00
+ 18500 |   5000 | 12:00:00
+ 21400 |   3500 | 13:00:00
+ 16400 |   4800 | 14:00:00
+ 17400 |   3900 | 15:00:00
+ 17400 |   4200 | 15:00:00
+ 15300 |   4500 | 17:00:00
+ 20500 |   4800 | 18:00:00
+ 16000 |   6000 | 19:00:00
+ 11200 |   5200 | 20:00:00
+(10 rows)
+
+select sum(salary) over (order by enroll_time desc range between '1 hour'::interval preceding and '2 hours'::interval following),
+	salary, enroll_time from empsalary;
+  sum  | salary | enroll_time 
+-------+--------+-------------
+ 16000 |   5200 | 20:00:00
+ 20500 |   6000 | 19:00:00
+ 15300 |   4800 | 18:00:00
+ 17400 |   4500 | 17:00:00
+ 16400 |   4200 | 15:00:00
+ 16400 |   3900 | 15:00:00
+ 21400 |   4800 | 14:00:00
+ 18500 |   3500 | 13:00:00
+ 13700 |   5000 | 12:00:00
+ 10200 |   5200 | 11:00:00
+(10 rows)
+
+select sum(salary) over (order by enroll_time desc range between '1 hour'::interval following and '2 hours'::interval following),
+	salary, enroll_time from empsalary;
+  sum  | salary | enroll_time 
+-------+--------+-------------
+ 10800 |   5200 | 20:00:00
+  9300 |   6000 | 19:00:00
+  4500 |   4800 | 18:00:00
+  8100 |   4500 | 17:00:00
+  8300 |   4200 | 15:00:00
+  8300 |   3900 | 15:00:00
+  8500 |   4800 | 14:00:00
+ 10200 |   3500 | 13:00:00
+  5200 |   5000 | 12:00:00
+       |   5200 | 11:00:00
+(10 rows)
+
+select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following
+	exclude current row), salary, enroll_time from empsalary;
+  sum  | salary | enroll_time 
+-------+--------+-------------
+  8500 |   5200 | 11:00:00
+ 13500 |   5000 | 12:00:00
+ 17900 |   3500 | 13:00:00
+ 11600 |   4800 | 14:00:00
+ 13500 |   3900 | 15:00:00
+ 13200 |   4200 | 15:00:00
+ 10800 |   4500 | 17:00:00
+ 15700 |   4800 | 18:00:00
+ 10000 |   6000 | 19:00:00
+  6000 |   5200 | 20:00:00
+(10 rows)
+
+select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following
+	exclude group), salary, enroll_time from empsalary;
+  sum  | salary | enroll_time 
+-------+--------+-------------
+  8500 |   5200 | 11:00:00
+ 13500 |   5000 | 12:00:00
+ 17900 |   3500 | 13:00:00
+ 11600 |   4800 | 14:00:00
+  9300 |   3900 | 15:00:00
+  9300 |   4200 | 15:00:00
+ 10800 |   4500 | 17:00:00
+ 15700 |   4800 | 18:00:00
+ 10000 |   6000 | 19:00:00
+  6000 |   5200 | 20:00:00
+(10 rows)
+
+select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following
+	exclude ties), salary, enroll_time from empsalary;
+  sum  | salary | enroll_time 
+-------+--------+-------------
+ 13700 |   5200 | 11:00:00
+ 18500 |   5000 | 12:00:00
+ 21400 |   3500 | 13:00:00
+ 16400 |   4800 | 14:00:00
+ 13200 |   3900 | 15:00:00
+ 13500 |   4200 | 15:00:00
+ 15300 |   4500 | 17:00:00
+ 20500 |   4800 | 18:00:00
+ 16000 |   6000 | 19:00:00
+ 11200 |   5200 | 20:00:00
+(10 rows)
+
+select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following),
+	salary, enroll_timetz from empsalary;
+  sum  | salary | enroll_timetz 
+-------+--------+---------------
+ 13700 |   5200 | 11:00:00+01
+ 18500 |   5000 | 12:00:00+01
+ 21400 |   3500 | 13:00:00+01
+ 16400 |   4800 | 14:00:00+01
+ 17400 |   3900 | 15:00:00+01
+ 17400 |   4200 | 15:00:00+01
+ 15300 |   4500 | 17:00:00+01
+ 20500 |   4800 | 18:00:00+01
+ 16000 |   6000 | 19:00:00+01
+ 11200 |   5200 | 20:00:00+01
+(10 rows)
+
+select sum(salary) over (order by enroll_timetz desc range between '1 hour'::interval preceding and '2 hours'::interval following),
+	salary, enroll_timetz from empsalary;
+  sum  | salary | enroll_timetz 
+-------+--------+---------------
+ 16000 |   5200 | 20:00:00+01
+ 20500 |   6000 | 19:00:00+01
+ 15300 |   4800 | 18:00:00+01
+ 17400 |   4500 | 17:00:00+01
+ 16400 |   4200 | 15:00:00+01
+ 16400 |   3900 | 15:00:00+01
+ 21400 |   4800 | 14:00:00+01
+ 18500 |   3500 | 13:00:00+01
+ 13700 |   5000 | 12:00:00+01
+ 10200 |   5200 | 11:00:00+01
+(10 rows)
+
+select sum(salary) over (order by enroll_timetz desc range between '1 hour'::interval following and '2 hours'::interval following),
+	salary, enroll_timetz from empsalary;
+  sum  | salary | enroll_timetz 
+-------+--------+---------------
+ 10800 |   5200 | 20:00:00+01
+  9300 |   6000 | 19:00:00+01
+  4500 |   4800 | 18:00:00+01
+  8100 |   4500 | 17:00:00+01
+  8300 |   4200 | 15:00:00+01
+  8300 |   3900 | 15:00:00+01
+  8500 |   4800 | 14:00:00+01
+ 10200 |   3500 | 13:00:00+01
+  5200 |   5000 | 12:00:00+01
+       |   5200 | 11:00:00+01
+(10 rows)
+
+select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following
+	exclude current row), salary, enroll_timetz from empsalary;
+  sum  | salary | enroll_timetz 
+-------+--------+---------------
+  8500 |   5200 | 11:00:00+01
+ 13500 |   5000 | 12:00:00+01
+ 17900 |   3500 | 13:00:00+01
+ 11600 |   4800 | 14:00:00+01
+ 13500 |   3900 | 15:00:00+01
+ 13200 |   4200 | 15:00:00+01
+ 10800 |   4500 | 17:00:00+01
+ 15700 |   4800 | 18:00:00+01
+ 10000 |   6000 | 19:00:00+01
+  6000 |   5200 | 20:00:00+01
+(10 rows)
+
+select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following
+	exclude group), salary, enroll_timetz from empsalary;
+  sum  | salary | enroll_timetz 
+-------+--------+---------------
+  8500 |   5200 | 11:00:00+01
+ 13500 |   5000 | 12:00:00+01
+ 17900 |   3500 | 13:00:00+01
+ 11600 |   4800 | 14:00:00+01
+  9300 |   3900 | 15:00:00+01
+  9300 |   4200 | 15:00:00+01
+ 10800 |   4500 | 17:00:00+01
+ 15700 |   4800 | 18:00:00+01
+ 10000 |   6000 | 19:00:00+01
+  6000 |   5200 | 20:00:00+01
+(10 rows)
+
+select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following
+	exclude ties), salary, enroll_timetz from empsalary;
+  sum  | salary | enroll_timetz 
+-------+--------+---------------
+ 13700 |   5200 | 11:00:00+01
+ 18500 |   5000 | 12:00:00+01
+ 21400 |   3500 | 13:00:00+01
+ 16400 |   4800 | 14:00:00+01
+ 13200 |   3900 | 15:00:00+01
+ 13500 |   4200 | 15:00:00+01
+ 15300 |   4500 | 17:00:00+01
+ 20500 |   4800 | 18:00:00+01
+ 16000 |   6000 | 19:00:00+01
+ 11200 |   5200 | 20:00:00+01
+(10 rows)
+
+select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following),
+	salary, enroll_interval from empsalary;
+  sum  | salary | enroll_interval 
+-------+--------+-----------------
+ 13700 |   5200 | @ 1 year
+ 18500 |   5000 | @ 2 years
+ 21400 |   3500 | @ 3 years
+ 16400 |   4800 | @ 4 years
+ 17400 |   3900 | @ 5 years
+ 17400 |   4200 | @ 5 years
+ 15300 |   4500 | @ 7 years
+ 20500 |   4800 | @ 8 years
+ 16000 |   6000 | @ 9 years
+ 11200 |   5200 | @ 10 years
+(10 rows)
+
+select sum(salary) over (order by enroll_interval desc range between '1 year'::interval preceding and '2 years'::interval following),
+	salary, enroll_interval from empsalary;
+  sum  | salary | enroll_interval 
+-------+--------+-----------------
+ 16000 |   5200 | @ 10 years
+ 20500 |   6000 | @ 9 years
+ 15300 |   4800 | @ 8 years
+ 17400 |   4500 | @ 7 years
+ 16400 |   4200 | @ 5 years
+ 16400 |   3900 | @ 5 years
+ 21400 |   4800 | @ 4 years
+ 18500 |   3500 | @ 3 years
+ 13700 |   5000 | @ 2 years
+ 10200 |   5200 | @ 1 year
+(10 rows)
+
+select sum(salary) over (order by enroll_interval desc range between '1 year'::interval following and '2 years'::interval following),
+	salary, enroll_interval from empsalary;
+  sum  | salary | enroll_interval 
+-------+--------+-----------------
+ 10800 |   5200 | @ 10 years
+  9300 |   6000 | @ 9 years
+  4500 |   4800 | @ 8 years
+  8100 |   4500 | @ 7 years
+  8300 |   4200 | @ 5 years
+  8300 |   3900 | @ 5 years
+  8500 |   4800 | @ 4 years
+ 10200 |   3500 | @ 3 years
+  5200 |   5000 | @ 2 years
+       |   5200 | @ 1 year
+(10 rows)
+
+select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following
+	exclude current row), salary, enroll_interval from empsalary;
+  sum  | salary | enroll_interval 
+-------+--------+-----------------
+  8500 |   5200 | @ 1 year
+ 13500 |   5000 | @ 2 years
+ 17900 |   3500 | @ 3 years
+ 11600 |   4800 | @ 4 years
+ 13500 |   3900 | @ 5 years
+ 13200 |   4200 | @ 5 years
+ 10800 |   4500 | @ 7 years
+ 15700 |   4800 | @ 8 years
+ 10000 |   6000 | @ 9 years
+  6000 |   5200 | @ 10 years
+(10 rows)
+
+select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following
+	exclude group), salary, enroll_interval from empsalary;
+  sum  | salary | enroll_interval 
+-------+--------+-----------------
+  8500 |   5200 | @ 1 year
+ 13500 |   5000 | @ 2 years
+ 17900 |   3500 | @ 3 years
+ 11600 |   4800 | @ 4 years
+  9300 |   3900 | @ 5 years
+  9300 |   4200 | @ 5 years
+ 10800 |   4500 | @ 7 years
+ 15700 |   4800 | @ 8 years
+ 10000 |   6000 | @ 9 years
+  6000 |   5200 | @ 10 years
+(10 rows)
+
+select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following
+	exclude ties), salary, enroll_interval from empsalary;
+  sum  | salary | enroll_interval 
+-------+--------+-----------------
+ 13700 |   5200 | @ 1 year
+ 18500 |   5000 | @ 2 years
+ 21400 |   3500 | @ 3 years
+ 16400 |   4800 | @ 4 years
+ 13200 |   3900 | @ 5 years
+ 13500 |   4200 | @ 5 years
+ 15300 |   4500 | @ 7 years
+ 20500 |   4800 | @ 8 years
+ 16000 |   6000 | @ 9 years
+ 11200 |   5200 | @ 10 years
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following),
+	salary, enroll_timestamptz from empsalary;
+  sum  | salary |      enroll_timestamptz      
+-------+--------+------------------------------
+ 18500 |   5200 | Thu Oct 19 10:23:54 2000 PDT
+ 22400 |   5000 | Fri Oct 19 10:23:54 2001 PDT
+ 22400 |   3500 | Fri Oct 19 10:23:54 2001 PDT
+ 21400 |   4800 | Sat Oct 19 10:23:54 2002 PDT
+ 17400 |   3900 | Sun Oct 19 10:23:54 2003 PDT
+ 17400 |   4200 | Tue Oct 19 10:23:54 2004 PDT
+ 19500 |   4500 | Wed Oct 19 10:23:54 2005 PDT
+ 20500 |   4800 | Thu Oct 19 10:23:54 2006 PDT
+ 16000 |   6000 | Fri Oct 19 10:23:54 2007 PDT
+ 11200 |   5200 | Sun Oct 19 10:23:54 2008 PDT
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamptz desc range between '1 year'::interval preceding and '2 years'::interval following),
+	salary, enroll_timestamptz from empsalary;
+  sum  | salary |      enroll_timestamptz      
+-------+--------+------------------------------
+ 16000 |   5200 | Sun Oct 19 10:23:54 2008 PDT
+ 20500 |   6000 | Fri Oct 19 10:23:54 2007 PDT
+ 19500 |   4800 | Thu Oct 19 10:23:54 2006 PDT
+ 17400 |   4500 | Wed Oct 19 10:23:54 2005 PDT
+ 17400 |   4200 | Tue Oct 19 10:23:54 2004 PDT
+ 21400 |   3900 | Sun Oct 19 10:23:54 2003 PDT
+ 22400 |   4800 | Sat Oct 19 10:23:54 2002 PDT
+ 18500 |   3500 | Fri Oct 19 10:23:54 2001 PDT
+ 18500 |   5000 | Fri Oct 19 10:23:54 2001 PDT
+ 13700 |   5200 | Thu Oct 19 10:23:54 2000 PDT
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamptz desc range between '1 year'::interval following and '2 years'::interval following),
+	salary, enroll_timestamptz from empsalary;
+  sum  | salary |      enroll_timestamptz      
+-------+--------+------------------------------
+ 10800 |   5200 | Sun Oct 19 10:23:54 2008 PDT
+  9300 |   6000 | Fri Oct 19 10:23:54 2007 PDT
+  8700 |   4800 | Thu Oct 19 10:23:54 2006 PDT
+  8100 |   4500 | Wed Oct 19 10:23:54 2005 PDT
+  8700 |   4200 | Tue Oct 19 10:23:54 2004 PDT
+ 13300 |   3900 | Sun Oct 19 10:23:54 2003 PDT
+ 13700 |   4800 | Sat Oct 19 10:23:54 2002 PDT
+  5200 |   3500 | Fri Oct 19 10:23:54 2001 PDT
+  5200 |   5000 | Fri Oct 19 10:23:54 2001 PDT
+       |   5200 | Thu Oct 19 10:23:54 2000 PDT
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following
+	exclude current row), salary, enroll_timestamptz from empsalary;
+  sum  | salary |      enroll_timestamptz      
+-------+--------+------------------------------
+ 13300 |   5200 | Thu Oct 19 10:23:54 2000 PDT
+ 17400 |   5000 | Fri Oct 19 10:23:54 2001 PDT
+ 18900 |   3500 | Fri Oct 19 10:23:54 2001 PDT
+ 16600 |   4800 | Sat Oct 19 10:23:54 2002 PDT
+ 13500 |   3900 | Sun Oct 19 10:23:54 2003 PDT
+ 13200 |   4200 | Tue Oct 19 10:23:54 2004 PDT
+ 15000 |   4500 | Wed Oct 19 10:23:54 2005 PDT
+ 15700 |   4800 | Thu Oct 19 10:23:54 2006 PDT
+ 10000 |   6000 | Fri Oct 19 10:23:54 2007 PDT
+  6000 |   5200 | Sun Oct 19 10:23:54 2008 PDT
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following
+	exclude group), salary, enroll_timestamptz from empsalary;
+  sum  | salary |      enroll_timestamptz      
+-------+--------+------------------------------
+ 13300 |   5200 | Thu Oct 19 10:23:54 2000 PDT
+ 13900 |   5000 | Fri Oct 19 10:23:54 2001 PDT
+ 13900 |   3500 | Fri Oct 19 10:23:54 2001 PDT
+ 16600 |   4800 | Sat Oct 19 10:23:54 2002 PDT
+ 13500 |   3900 | Sun Oct 19 10:23:54 2003 PDT
+ 13200 |   4200 | Tue Oct 19 10:23:54 2004 PDT
+ 15000 |   4500 | Wed Oct 19 10:23:54 2005 PDT
+ 15700 |   4800 | Thu Oct 19 10:23:54 2006 PDT
+ 10000 |   6000 | Fri Oct 19 10:23:54 2007 PDT
+  6000 |   5200 | Sun Oct 19 10:23:54 2008 PDT
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following
+	exclude ties), salary, enroll_timestamptz from empsalary;
+  sum  | salary |      enroll_timestamptz      
+-------+--------+------------------------------
+ 18500 |   5200 | Thu Oct 19 10:23:54 2000 PDT
+ 18900 |   5000 | Fri Oct 19 10:23:54 2001 PDT
+ 17400 |   3500 | Fri Oct 19 10:23:54 2001 PDT
+ 21400 |   4800 | Sat Oct 19 10:23:54 2002 PDT
+ 17400 |   3900 | Sun Oct 19 10:23:54 2003 PDT
+ 17400 |   4200 | Tue Oct 19 10:23:54 2004 PDT
+ 19500 |   4500 | Wed Oct 19 10:23:54 2005 PDT
+ 20500 |   4800 | Thu Oct 19 10:23:54 2006 PDT
+ 16000 |   6000 | Fri Oct 19 10:23:54 2007 PDT
+ 11200 |   5200 | Sun Oct 19 10:23:54 2008 PDT
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following),
+	salary, enroll_timestamp from empsalary;
+  sum  | salary |     enroll_timestamp     
+-------+--------+--------------------------
+ 18500 |   5200 | Thu Oct 19 10:23:54 2000
+ 22400 |   5000 | Fri Oct 19 10:23:54 2001
+ 22400 |   3500 | Fri Oct 19 10:23:54 2001
+ 21400 |   4800 | Sat Oct 19 10:23:54 2002
+ 17400 |   3900 | Sun Oct 19 10:23:54 2003
+ 17400 |   4200 | Tue Oct 19 10:23:54 2004
+ 19500 |   4500 | Wed Oct 19 10:23:54 2005
+ 20500 |   4800 | Thu Oct 19 10:23:54 2006
+ 16000 |   6000 | Fri Oct 19 10:23:54 2007
+ 11200 |   5200 | Sun Oct 19 10:23:54 2008
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamp desc range between '1 year'::interval preceding and '2 years'::interval following),
+	salary, enroll_timestamp from empsalary;
+  sum  | salary |     enroll_timestamp     
+-------+--------+--------------------------
+ 16000 |   5200 | Sun Oct 19 10:23:54 2008
+ 20500 |   6000 | Fri Oct 19 10:23:54 2007
+ 19500 |   4800 | Thu Oct 19 10:23:54 2006
+ 17400 |   4500 | Wed Oct 19 10:23:54 2005
+ 17400 |   4200 | Tue Oct 19 10:23:54 2004
+ 21400 |   3900 | Sun Oct 19 10:23:54 2003
+ 22400 |   4800 | Sat Oct 19 10:23:54 2002
+ 18500 |   3500 | Fri Oct 19 10:23:54 2001
+ 18500 |   5000 | Fri Oct 19 10:23:54 2001
+ 13700 |   5200 | Thu Oct 19 10:23:54 2000
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamp desc range between '1 year'::interval following and '2 years'::interval following),
+	salary, enroll_timestamp from empsalary;
+  sum  | salary |     enroll_timestamp     
+-------+--------+--------------------------
+ 10800 |   5200 | Sun Oct 19 10:23:54 2008
+  9300 |   6000 | Fri Oct 19 10:23:54 2007
+  8700 |   4800 | Thu Oct 19 10:23:54 2006
+  8100 |   4500 | Wed Oct 19 10:23:54 2005
+  8700 |   4200 | Tue Oct 19 10:23:54 2004
+ 13300 |   3900 | Sun Oct 19 10:23:54 2003
+ 13700 |   4800 | Sat Oct 19 10:23:54 2002
+  5200 |   3500 | Fri Oct 19 10:23:54 2001
+  5200 |   5000 | Fri Oct 19 10:23:54 2001
+       |   5200 | Thu Oct 19 10:23:54 2000
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following
+	exclude current row), salary, enroll_timestamp from empsalary;
+  sum  | salary |     enroll_timestamp     
+-------+--------+--------------------------
+ 13300 |   5200 | Thu Oct 19 10:23:54 2000
+ 17400 |   5000 | Fri Oct 19 10:23:54 2001
+ 18900 |   3500 | Fri Oct 19 10:23:54 2001
+ 16600 |   4800 | Sat Oct 19 10:23:54 2002
+ 13500 |   3900 | Sun Oct 19 10:23:54 2003
+ 13200 |   4200 | Tue Oct 19 10:23:54 2004
+ 15000 |   4500 | Wed Oct 19 10:23:54 2005
+ 15700 |   4800 | Thu Oct 19 10:23:54 2006
+ 10000 |   6000 | Fri Oct 19 10:23:54 2007
+  6000 |   5200 | Sun Oct 19 10:23:54 2008
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following
+	exclude group), salary, enroll_timestamp from empsalary;
+  sum  | salary |     enroll_timestamp     
+-------+--------+--------------------------
+ 13300 |   5200 | Thu Oct 19 10:23:54 2000
+ 13900 |   5000 | Fri Oct 19 10:23:54 2001
+ 13900 |   3500 | Fri Oct 19 10:23:54 2001
+ 16600 |   4800 | Sat Oct 19 10:23:54 2002
+ 13500 |   3900 | Sun Oct 19 10:23:54 2003
+ 13200 |   4200 | Tue Oct 19 10:23:54 2004
+ 15000 |   4500 | Wed Oct 19 10:23:54 2005
+ 15700 |   4800 | Thu Oct 19 10:23:54 2006
+ 10000 |   6000 | Fri Oct 19 10:23:54 2007
+  6000 |   5200 | Sun Oct 19 10:23:54 2008
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following
+	exclude ties), salary, enroll_timestamp from empsalary;
+  sum  | salary |     enroll_timestamp     
+-------+--------+--------------------------
+ 18500 |   5200 | Thu Oct 19 10:23:54 2000
+ 18900 |   5000 | Fri Oct 19 10:23:54 2001
+ 17400 |   3500 | Fri Oct 19 10:23:54 2001
+ 21400 |   4800 | Sat Oct 19 10:23:54 2002
+ 17400 |   3900 | Sun Oct 19 10:23:54 2003
+ 17400 |   4200 | Tue Oct 19 10:23:54 2004
+ 19500 |   4500 | Wed Oct 19 10:23:54 2005
+ 20500 |   4800 | Thu Oct 19 10:23:54 2006
+ 16000 |   6000 | Fri Oct 19 10:23:54 2007
+ 11200 |   5200 | Sun Oct 19 10:23:54 2008
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamp range between current row and '2 years'::interval following),
+	salary, enroll_timestamp from empsalary;
+  sum  | salary |     enroll_timestamp     
+-------+--------+--------------------------
+ 18500 |   5200 | Thu Oct 19 10:23:54 2000
+ 17200 |   5000 | Fri Oct 19 10:23:54 2001
+ 17200 |   3500 | Fri Oct 19 10:23:54 2001
+ 12900 |   4800 | Sat Oct 19 10:23:54 2002
+ 12600 |   3900 | Sun Oct 19 10:23:54 2003
+ 13500 |   4200 | Tue Oct 19 10:23:54 2004
+ 15300 |   4500 | Wed Oct 19 10:23:54 2005
+ 16000 |   4800 | Thu Oct 19 10:23:54 2006
+ 11200 |   6000 | Fri Oct 19 10:23:54 2007
+  5200 |   5200 | Sun Oct 19 10:23:54 2008
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and current row),
+	salary, enroll_timestamp from empsalary;
+  sum  | salary |     enroll_timestamp     
+-------+--------+--------------------------
+  5200 |   5200 | Thu Oct 19 10:23:54 2000
+ 13700 |   5000 | Fri Oct 19 10:23:54 2001
+ 13700 |   3500 | Fri Oct 19 10:23:54 2001
+ 13300 |   4800 | Sat Oct 19 10:23:54 2002
+  8700 |   3900 | Sun Oct 19 10:23:54 2003
+  8100 |   4200 | Tue Oct 19 10:23:54 2004
+  8700 |   4500 | Wed Oct 19 10:23:54 2005
+  9300 |   4800 | Thu Oct 19 10:23:54 2006
+ 10800 |   6000 | Fri Oct 19 10:23:54 2007
+ 11200 |   5200 | Sun Oct 19 10:23:54 2008
+(10 rows)
+
+select sum(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following),
+	salary, enroll_date from empsalary;
+  sum  | salary | enroll_date 
+-------+--------+-------------
+ 34900 |   5000 | 10-01-2006
+ 34900 |   6000 | 10-01-2006
+ 38400 |   3900 | 12-23-2006
+ 47100 |   4800 | 08-01-2007
+ 47100 |   5200 | 08-01-2007
+ 47100 |   4800 | 08-08-2007
+ 47100 |   5200 | 08-15-2007
+ 47100 |   3500 | 12-10-2007
+ 47100 |   4500 | 01-01-2008
+ 47100 |   4200 | 01-01-2008
+(10 rows)
+
+select sum(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following
+	exclude current row), salary, enroll_date from empsalary;
+  sum  | salary | enroll_date 
+-------+--------+-------------
+ 29900 |   5000 | 10-01-2006
+ 28900 |   6000 | 10-01-2006
+ 34500 |   3900 | 12-23-2006
+ 42300 |   4800 | 08-01-2007
+ 41900 |   5200 | 08-01-2007
+ 42300 |   4800 | 08-08-2007
+ 41900 |   5200 | 08-15-2007
+ 43600 |   3500 | 12-10-2007
+ 42600 |   4500 | 01-01-2008
+ 42900 |   4200 | 01-01-2008
+(10 rows)
+
+select sum(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following
+	exclude group), salary, enroll_date from empsalary;
+  sum  | salary | enroll_date 
+-------+--------+-------------
+ 23900 |   5000 | 10-01-2006
+ 23900 |   6000 | 10-01-2006
+ 34500 |   3900 | 12-23-2006
+ 37100 |   4800 | 08-01-2007
+ 37100 |   5200 | 08-01-2007
+ 42300 |   4800 | 08-08-2007
+ 41900 |   5200 | 08-15-2007
+ 43600 |   3500 | 12-10-2007
+ 38400 |   4500 | 01-01-2008
+ 38400 |   4200 | 01-01-2008
+(10 rows)
+
+select sum(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following
+	exclude ties), salary, enroll_date from empsalary;
+  sum  | salary | enroll_date 
+-------+--------+-------------
+ 28900 |   5000 | 10-01-2006
+ 29900 |   6000 | 10-01-2006
+ 38400 |   3900 | 12-23-2006
+ 41900 |   4800 | 08-01-2007
+ 42300 |   5200 | 08-01-2007
+ 47100 |   4800 | 08-08-2007
+ 47100 |   5200 | 08-15-2007
+ 47100 |   3500 | 12-10-2007
+ 42900 |   4500 | 01-01-2008
+ 42600 |   4200 | 01-01-2008
+(10 rows)
+
+select first_value(salary) over(order by salary range between 1000 preceding and 1000 following),
+	lead(salary) over(order by salary range between 1000 preceding and 1000 following),
+	nth_value(salary, 1) over(order by salary range between 1000 preceding and 1000 following),
+	salary from empsalary;
+ first_value | lead | nth_value | salary 
+-------------+------+-----------+--------
+        3500 | 3900 |      3500 |   3500
+        3500 | 4200 |      3500 |   3900
+        3500 | 4500 |      3500 |   4200
+        3500 | 4800 |      3500 |   4500
+        3900 | 4800 |      3900 |   4800
+        3900 | 5000 |      3900 |   4800
+        4200 | 5200 |      4200 |   5000
+        4200 | 5200 |      4200 |   5200
+        4200 | 6000 |      4200 |   5200
+        5000 |      |      5000 |   6000
+(10 rows)
+
+select last_value(salary) over(order by salary range between 1000 preceding and 1000 following),
+	lag(salary) over(order by salary range between 1000 preceding and 1000 following),
+	salary from empsalary;
+ last_value | lag  | salary 
+------------+------+--------
+       4500 |      |   3500
+       4800 | 3500 |   3900
+       5200 | 3900 |   4200
+       5200 | 4200 |   4500
+       5200 | 4500 |   4800
+       5200 | 4800 |   4800
+       6000 | 4800 |   5000
+       6000 | 5000 |   5200
+       6000 | 5200 |   5200
+       6000 | 5200 |   6000
+(10 rows)
+
+select first_value(salary) over(order by salary range between 1000 following and 3000 following
+	exclude current row),
+	lead(salary) over(order by salary range between 1000 following and 3000 following exclude ties),
+	nth_value(salary, 1) over(order by salary range between 1000 following and 3000 following
+	exclude ties),
+	salary from empsalary;
+ first_value | lead | nth_value | salary 
+-------------+------+-----------+--------
+        4500 | 3900 |      4500 |   3500
+        5000 | 4200 |      5000 |   3900
+        5200 | 4500 |      5200 |   4200
+        6000 | 4800 |      6000 |   4500
+        6000 | 4800 |      6000 |   4800
+        6000 | 5000 |      6000 |   4800
+        6000 | 5200 |      6000 |   5000
+             | 5200 |           |   5200
+             | 6000 |           |   5200
+             |      |           |   6000
+(10 rows)
+
+select last_value(salary) over(order by salary range between 1000 following and 3000 following
+	exclude group),
+	lag(salary) over(order by salary range between 1000 following and 3000 following exclude group),
+	salary from empsalary;
+ last_value | lag  | salary 
+------------+------+--------
+       6000 |      |   3500
+       6000 | 3500 |   3900
+       6000 | 3900 |   4200
+       6000 | 4200 |   4500
+       6000 | 4500 |   4800
+       6000 | 4800 |   4800
+       6000 | 4800 |   5000
+            | 5000 |   5200
+            | 5200 |   5200
+            | 5200 |   6000
+(10 rows)
+
+select first_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following
+	exclude ties),
+	last_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following),
+	salary, enroll_date from empsalary;
+ first_value | last_value | salary | enroll_date 
+-------------+------------+--------+-------------
+        5000 |       5200 |   5000 | 10-01-2006
+             |       5200 |   6000 | 10-01-2006
+        5000 |       3500 |   3900 | 12-23-2006
+        5000 |       4200 |   4800 | 08-01-2007
+        5000 |       4200 |   5200 | 08-01-2007
+        5000 |       4200 |   4800 | 08-08-2007
+        5000 |       4200 |   5200 | 08-15-2007
+        5000 |       4200 |   3500 | 12-10-2007
+        5000 |       4200 |   4500 | 01-01-2008
+        5000 |       4200 |   4200 | 01-01-2008
+(10 rows)
+
+-- RANGE BETWEEN with values negative tests
+select sum(salary) over (order by enroll_timestamp, enroll_date range between '1 year'::interval preceding and '2 years'::interval following
+	exclude ties), salary, enroll_timestamp from empsalary;
+ERROR:  RANGE clause requires exactly one ORDER BY column
+LINE 1: select sum(salary) over (order by enroll_timestamp, enroll_d...
+                                ^
+select sum(salary) over (range between '1 year'::interval preceding and '2 years'::interval following
+	exclude ties), salary, enroll_timestamp from empsalary;
+ERROR:  RANGE clause requires exactly one ORDER BY column
+LINE 1: select sum(salary) over (range between '1 year'::interval pr...
+                                ^
+select sum(salary) over (order by depname range between '1 year'::interval preceding and '2 years'::interval following
+	exclude ties), salary, enroll_timestamp from empsalary;
+ERROR:  no in_range function for given types
+LINE 1: select sum(salary) over (order by depname range between '1 y...
+                                ^
+select max(enroll_date) over (order by enroll_timestamp range between 1 preceding and 2 following
+	exclude ties), salary, enroll_timestamp from empsalary;
+ERROR:  no in_range function for given types
+LINE 1: select max(enroll_date) over (order by enroll_timestamp rang...
+                                     ^
+select max(enroll_date) over (order by salary range between -1 preceding and 2 following
+	exclude ties), salary, enroll_timestamp from empsalary;
+ERROR:  RANGE offsets cannot be negative. invalid value -1
+select max(enroll_date) over (order by salary range between 1 preceding and -2 following
+	exclude ties), salary, enroll_timestamp from empsalary;
+ERROR:  RANGE offsets cannot be negative. invalid value -2
+select max(enroll_date) over (order by salary range between '1 year'::interval preceding and '2 years'::interval following
+	exclude ties), salary, enroll_timestamp from empsalary;
+ERROR:  no in_range function for given types
+LINE 1: select max(enroll_date) over (order by salary range between ...
+                                     ^
+-- GROUPS tests
+SELECT sum(unique1) over (order by four groups between unbounded preceding and current row),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+  12 |       0 |    0
+  12 |       8 |    0
+  12 |       4 |    0
+  27 |       5 |    1
+  27 |       9 |    1
+  27 |       1 |    1
+  35 |       6 |    2
+  35 |       2 |    2
+  45 |       3 |    3
+  45 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between unbounded preceding and unbounded following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+  45 |       0 |    0
+  45 |       8 |    0
+  45 |       4 |    0
+  45 |       5 |    1
+  45 |       9 |    1
+  45 |       1 |    1
+  45 |       6 |    2
+  45 |       2 |    2
+  45 |       3 |    3
+  45 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between current row and unbounded following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+  45 |       0 |    0
+  45 |       8 |    0
+  45 |       4 |    0
+  33 |       5 |    1
+  33 |       9 |    1
+  33 |       1 |    1
+  18 |       6 |    2
+  18 |       2 |    2
+  10 |       3 |    3
+  10 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 1 preceding and unbounded following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+  45 |       0 |    0
+  45 |       8 |    0
+  45 |       4 |    0
+  45 |       5 |    1
+  45 |       9 |    1
+  45 |       1 |    1
+  33 |       6 |    2
+  33 |       2 |    2
+  18 |       3 |    3
+  18 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 1 following and unbounded following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+  33 |       0 |    0
+  33 |       8 |    0
+  33 |       4 |    0
+  18 |       5 |    1
+  18 |       9 |    1
+  18 |       1 |    1
+  10 |       6 |    2
+  10 |       2 |    2
+     |       3 |    3
+     |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between unbounded preceding and 2 following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+  35 |       0 |    0
+  35 |       8 |    0
+  35 |       4 |    0
+  45 |       5 |    1
+  45 |       9 |    1
+  45 |       1 |    1
+  45 |       6 |    2
+  45 |       2 |    2
+  45 |       3 |    3
+  45 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 preceding),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+     |       0 |    0
+     |       8 |    0
+     |       4 |    0
+  12 |       5 |    1
+  12 |       9 |    1
+  12 |       1 |    1
+  27 |       6 |    2
+  27 |       2 |    2
+  23 |       3 |    3
+  23 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+  27 |       0 |    0
+  27 |       8 |    0
+  27 |       4 |    0
+  35 |       5 |    1
+  35 |       9 |    1
+  35 |       1 |    1
+  45 |       6 |    2
+  45 |       2 |    2
+  33 |       3 |    3
+  33 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 0 preceding and 0 following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+  12 |       0 |    0
+  12 |       8 |    0
+  12 |       4 |    0
+  15 |       5 |    1
+  15 |       9 |    1
+  15 |       1 |    1
+   8 |       6 |    2
+   8 |       2 |    2
+  10 |       3 |    3
+  10 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following
+	exclude current row), unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+  27 |       0 |    0
+  19 |       8 |    0
+  23 |       4 |    0
+  30 |       5 |    1
+  26 |       9 |    1
+  34 |       1 |    1
+  39 |       6 |    2
+  43 |       2 |    2
+  30 |       3 |    3
+  26 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following
+	exclude group), unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+  15 |       0 |    0
+  15 |       8 |    0
+  15 |       4 |    0
+  20 |       5 |    1
+  20 |       9 |    1
+  20 |       1 |    1
+  37 |       6 |    2
+  37 |       2 |    2
+  23 |       3 |    3
+  23 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following
+	exclude ties), unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+  15 |       0 |    0
+  23 |       8 |    0
+  19 |       4 |    0
+  25 |       5 |    1
+  29 |       9 |    1
+  21 |       1 |    1
+  43 |       6 |    2
+  39 |       2 |    2
+  26 |       3 |    3
+  30 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (partition by ten
+	order by four groups between 0 preceding and 0 following),unique1, four, ten
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four | ten 
+-----+---------+------+-----
+   0 |       0 |    0 |   0
+   1 |       1 |    1 |   1
+   2 |       2 |    2 |   2
+   3 |       3 |    3 |   3
+   4 |       4 |    0 |   4
+   5 |       5 |    1 |   5
+   6 |       6 |    2 |   6
+   7 |       7 |    3 |   7
+   8 |       8 |    0 |   8
+   9 |       9 |    1 |   9
+(10 rows)
+
+SELECT sum(unique1) over (partition by ten
+	order by four groups between 0 preceding and 0 following exclude current row), unique1, four, ten
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four | ten 
+-----+---------+------+-----
+     |       0 |    0 |   0
+     |       1 |    1 |   1
+     |       2 |    2 |   2
+     |       3 |    3 |   3
+     |       4 |    0 |   4
+     |       5 |    1 |   5
+     |       6 |    2 |   6
+     |       7 |    3 |   7
+     |       8 |    0 |   8
+     |       9 |    1 |   9
+(10 rows)
+
+SELECT sum(unique1) over (partition by ten
+	order by four groups between 0 preceding and 0 following exclude group), unique1, four, ten
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four | ten 
+-----+---------+------+-----
+     |       0 |    0 |   0
+     |       1 |    1 |   1
+     |       2 |    2 |   2
+     |       3 |    3 |   3
+     |       4 |    0 |   4
+     |       5 |    1 |   5
+     |       6 |    2 |   6
+     |       7 |    3 |   7
+     |       8 |    0 |   8
+     |       9 |    1 |   9
+(10 rows)
+
+SELECT sum(unique1) over (partition by ten
+	order by four groups between 0 preceding and 0 following exclude ties), unique1, four, ten
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four | ten 
+-----+---------+------+-----
+   0 |       0 |    0 |   0
+   1 |       1 |    1 |   1
+   2 |       2 |    2 |   2
+   3 |       3 |    3 |   3
+   4 |       4 |    0 |   4
+   5 |       5 |    1 |   5
+   6 |       6 |    2 |   6
+   7 |       7 |    3 |   7
+   8 |       8 |    0 |   8
+   9 |       9 |    1 |   9
+(10 rows)
+
+select first_value(salary) over(order by enroll_date groups between 1 preceding and 1 following),
+	lead(salary) over(order by enroll_date groups between 1 preceding and 1 following),
+	nth_value(salary, 1) over(order by enroll_date groups between 1 preceding and 1 following),
+	salary, enroll_date from empsalary;
+ first_value | lead | nth_value | salary | enroll_date 
+-------------+------+-----------+--------+-------------
+        5000 | 6000 |      5000 |   5000 | 10-01-2006
+        5000 | 3900 |      5000 |   6000 | 10-01-2006
+        5000 | 4800 |      5000 |   3900 | 12-23-2006
+        3900 | 5200 |      3900 |   4800 | 08-01-2007
+        3900 | 4800 |      3900 |   5200 | 08-01-2007
+        4800 | 5200 |      4800 |   4800 | 08-08-2007
+        4800 | 3500 |      4800 |   5200 | 08-15-2007
+        5200 | 4500 |      5200 |   3500 | 12-10-2007
+        3500 | 4200 |      3500 |   4500 | 01-01-2008
+        3500 |      |      3500 |   4200 | 01-01-2008
+(10 rows)
+
+select last_value(salary) over(order by enroll_date groups between 1 preceding and 1 following),
+	lag(salary) over(order by enroll_date groups between 1 preceding and 1 following),
+	salary, enroll_date from empsalary;
+ last_value | lag  | salary | enroll_date 
+------------+------+--------+-------------
+       3900 |      |   5000 | 10-01-2006
+       3900 | 5000 |   6000 | 10-01-2006
+       5200 | 6000 |   3900 | 12-23-2006
+       4800 | 3900 |   4800 | 08-01-2007
+       4800 | 4800 |   5200 | 08-01-2007
+       5200 | 5200 |   4800 | 08-08-2007
+       3500 | 4800 |   5200 | 08-15-2007
+       4200 | 5200 |   3500 | 12-10-2007
+       4200 | 3500 |   4500 | 01-01-2008
+       4200 | 4500 |   4200 | 01-01-2008
+(10 rows)
+
+select first_value(salary) over(order by enroll_date groups between 1 following and 3 following
+	exclude current row),
+	lead(salary) over(order by enroll_date groups between 1 following and 3 following exclude ties),
+	nth_value(salary, 1) over(order by enroll_date groups between 1 following and 3 following
+	exclude ties),
+	salary, enroll_date from empsalary;
+ first_value | lead | nth_value | salary | enroll_date 
+-------------+------+-----------+--------+-------------
+        3900 | 6000 |      3900 |   5000 | 10-01-2006
+        3900 | 3900 |      3900 |   6000 | 10-01-2006
+        4800 | 4800 |      4800 |   3900 | 12-23-2006
+        4800 | 5200 |      4800 |   4800 | 08-01-2007
+        4800 | 4800 |      4800 |   5200 | 08-01-2007
+        5200 | 5200 |      5200 |   4800 | 08-08-2007
+        3500 | 3500 |      3500 |   5200 | 08-15-2007
+        4500 | 4500 |      4500 |   3500 | 12-10-2007
+             | 4200 |           |   4500 | 01-01-2008
+             |      |           |   4200 | 01-01-2008
+(10 rows)
+
+select last_value(salary) over(order by enroll_date groups between 1 following and 3 following
+	exclude group),
+	lag(salary) over(order by enroll_date groups between 1 following and 3 following exclude group),
+	salary, enroll_date from empsalary;
+ last_value | lag  | salary | enroll_date 
+------------+------+--------+-------------
+       4800 |      |   5000 | 10-01-2006
+       4800 | 5000 |   6000 | 10-01-2006
+       5200 | 6000 |   3900 | 12-23-2006
+       3500 | 3900 |   4800 | 08-01-2007
+       3500 | 4800 |   5200 | 08-01-2007
+       4200 | 5200 |   4800 | 08-08-2007
+       4200 | 4800 |   5200 | 08-15-2007
+       4200 | 5200 |   3500 | 12-10-2007
+            | 3500 |   4500 | 01-01-2008
+            | 4500 |   4200 | 01-01-2008
+(10 rows)
+
+-- Show differences in values mode between ROWS, RANGE, and GROUPS
+WITH cte (x) AS (
+        SELECT * FROM generate_series(1, 35, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x rows between 1 preceding and 1 following);
+ x  | sum 
+----+-----
+  1 |   4
+  3 |   9
+  5 |  15
+  7 |  21
+  9 |  27
+ 11 |  33
+ 13 |  39
+ 15 |  45
+ 17 |  51
+ 19 |  57
+ 21 |  63
+ 23 |  69
+ 25 |  75
+ 27 |  81
+ 29 |  87
+ 31 |  93
+ 33 |  99
+ 35 |  68
+(18 rows)
+
+WITH cte (x) AS (
+        SELECT * FROM generate_series(1, 35, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x range between 1 preceding and 1 following);
+ x  | sum 
+----+-----
+  1 |   1
+  3 |   3
+  5 |   5
+  7 |   7
+  9 |   9
+ 11 |  11
+ 13 |  13
+ 15 |  15
+ 17 |  17
+ 19 |  19
+ 21 |  21
+ 23 |  23
+ 25 |  25
+ 27 |  27
+ 29 |  29
+ 31 |  31
+ 33 |  33
+ 35 |  35
+(18 rows)
+
+WITH cte (x) AS (
+        SELECT * FROM generate_series(1, 35, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x groups between 1 preceding and 1 following);
+ x  | sum 
+----+-----
+  1 |   4
+  3 |   9
+  5 |  15
+  7 |  21
+  9 |  27
+ 11 |  33
+ 13 |  39
+ 15 |  45
+ 17 |  51
+ 19 |  57
+ 21 |  63
+ 23 |  69
+ 25 |  75
+ 27 |  81
+ 29 |  87
+ 31 |  93
+ 33 |  99
+ 35 |  68
+(18 rows)
+
+WITH cte (x) AS (
+        select 1 union all select 1 union all select 1 union all
+        SELECT * FROM generate_series(5, 49, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x rows between 1 preceding and 1 following);
+ x  | sum 
+----+-----
+  1 |   2
+  1 |   3
+  1 |   7
+  5 |  13
+  7 |  21
+  9 |  27
+ 11 |  33
+ 13 |  39
+ 15 |  45
+ 17 |  51
+ 19 |  57
+ 21 |  63
+ 23 |  69
+ 25 |  75
+ 27 |  81
+ 29 |  87
+ 31 |  93
+ 33 |  99
+ 35 | 105
+ 37 | 111
+ 39 | 117
+ 41 | 123
+ 43 | 129
+ 45 | 135
+ 47 | 141
+ 49 |  96
+(26 rows)
+
+WITH cte (x) AS (
+        select 1 union all select 1 union all select 1 union all
+        SELECT * FROM generate_series(5, 49, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x range between 1 preceding and 1 following);
+ x  | sum 
+----+-----
+  1 |   3
+  1 |   3
+  1 |   3
+  5 |   5
+  7 |   7
+  9 |   9
+ 11 |  11
+ 13 |  13
+ 15 |  15
+ 17 |  17
+ 19 |  19
+ 21 |  21
+ 23 |  23
+ 25 |  25
+ 27 |  27
+ 29 |  29
+ 31 |  31
+ 33 |  33
+ 35 |  35
+ 37 |  37
+ 39 |  39
+ 41 |  41
+ 43 |  43
+ 45 |  45
+ 47 |  47
+ 49 |  49
+(26 rows)
+
+WITH cte (x) AS (
+        select 1 union all select 1 union all select 1 union all
+        SELECT * FROM generate_series(5, 49, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x groups between 1 preceding and 1 following);
+ x  | sum 
+----+-----
+  1 |   8
+  1 |   8
+  1 |   8
+  5 |  15
+  7 |  21
+  9 |  27
+ 11 |  33
+ 13 |  39
+ 15 |  45
+ 17 |  51
+ 19 |  57
+ 21 |  63
+ 23 |  69
+ 25 |  75
+ 27 |  81
+ 29 |  87
+ 31 |  93
+ 33 |  99
+ 35 | 105
+ 37 | 111
+ 39 | 117
+ 41 | 123
+ 43 | 129
+ 45 | 135
+ 47 | 141
+ 49 |  96
+(26 rows)
+
 -- with UNION
 SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk2)s LIMIT 0;
  count 
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index e2a1a1cdd5..7826f41afd 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -6,20 +6,25 @@ CREATE TEMPORARY TABLE empsalary (
     depname varchar,
     empno bigint,
     salary int,
-    enroll_date date
+    enroll_date date,
+    enroll_time time,
+    enroll_timetz timetz,
+    enroll_interval interval,
+    enroll_timestamptz timestamptz,
+    enroll_timestamp timestamp
 );
 
 INSERT INTO empsalary VALUES
-('develop', 10, 5200, '2007-08-01'),
-('sales', 1, 5000, '2006-10-01'),
-('personnel', 5, 3500, '2007-12-10'),
-('sales', 4, 4800, '2007-08-08'),
-('personnel', 2, 3900, '2006-12-23'),
-('develop', 7, 4200, '2008-01-01'),
-('develop', 9, 4500, '2008-01-01'),
-('sales', 3, 4800, '2007-08-01'),
-('develop', 8, 6000, '2006-10-01'),
-('develop', 11, 5200, '2007-08-15');
+('develop', 10, 5200, '2007-08-01', '11:00', '11:00 BST', '1 year'::interval, TIMESTAMP '2000-10-19 10:23:54+01', TIMESTAMP '2000-10-19 10:23:54'),
+('sales', 1, 5000, '2006-10-01', '12:00', '12:00 BST', '2 years'::interval, TIMESTAMP '2001-10-19 10:23:54+01', TIMESTAMP '2001-10-19 10:23:54'),
+('personnel', 5, 3500, '2007-12-10', '13:00', '13:00 BST', '3 years'::interval, TIMESTAMP '2001-10-19 10:23:54+01', TIMESTAMP '2001-10-19 10:23:54'),
+('sales', 4, 4800, '2007-08-08', '14:00', '14:00 BST', '4 years'::interval, TIMESTAMP '2002-10-19 10:23:54+01', TIMESTAMP '2002-10-19 10:23:54'),
+('personnel', 2, 3900, '2006-12-23', '15:00', '15:00 BST', '5 years'::interval, TIMESTAMP '2003-10-19 10:23:54+01', TIMESTAMP '2003-10-19 10:23:54'),
+('develop', 7, 4200, '2008-01-01', '15:00', '15:00 BST', '5 years'::interval, TIMESTAMP '2004-10-19 10:23:54+01', TIMESTAMP '2004-10-19 10:23:54'),
+('develop', 9, 4500, '2008-01-01', '17:00', '17:00 BST', '7 years'::interval, TIMESTAMP '2005-10-19 10:23:54+01', TIMESTAMP '2005-10-19 10:23:54'),
+('sales', 3, 4800, '2007-08-01', '18:00', '18:00 BST', '8 years'::interval, TIMESTAMP '2006-10-19 10:23:54+01', TIMESTAMP '2006-10-19 10:23:54'),
+('develop', 8, 6000, '2006-10-01', '19:00', '19:00 BST', '9 years'::interval, TIMESTAMP '2007-10-19 10:23:54+01', TIMESTAMP '2007-10-19 10:23:54'),
+('develop', 11, 5200, '2007-08-15', '20:00', '20:00 BST', '10 years'::interval, TIMESTAMP '2008-10-19 10:23:54+01', TIMESTAMP '2008-10-19 10:23:54');
 
 SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
 
@@ -189,6 +194,22 @@ SELECT sum(unique1) over (rows between 2 preceding and 2 following),
 	unique1, four
 FROM tenk1 WHERE unique1 < 10;
 
+SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude no others),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude current row),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude group),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude ties),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
 SELECT sum(unique1) over (rows between 2 preceding and 1 preceding),
 	unique1, four
 FROM tenk1 WHERE unique1 < 10;
@@ -205,10 +226,17 @@ SELECT sum(unique1) over (w range between current row and unbounded following),
 	unique1, four
 FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
 
--- fail: not implemented yet
-SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding),
+SELECT sum(unique1) over (w range between unbounded preceding and current row exclude current row),
 	unique1, four
-FROM tenk1 WHERE unique1 < 10;
+FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
+
+SELECT sum(unique1) over (w range between unbounded preceding and current row exclude group),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
+
+SELECT sum(unique1) over (w range between unbounded preceding and current row exclude ties),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
 
 SELECT first_value(unique1) over w,
 	nth_value(unique1, 2) over w AS nth_2,
@@ -230,6 +258,394 @@ SELECT * FROM v_window;
 
 SELECT pg_get_viewdef('v_window');
 
+CREATE OR REPLACE TEMP VIEW v_window AS
+	SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following
+	exclude current row) as sum_rows FROM generate_series(1, 10) i;
+
+SELECT * FROM v_window;
+
+SELECT pg_get_viewdef('v_window');
+
+CREATE OR REPLACE TEMP VIEW v_window AS
+	SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following
+	exclude group) as sum_rows FROM generate_series(1, 10) i;
+
+SELECT * FROM v_window;
+
+SELECT pg_get_viewdef('v_window');
+
+CREATE OR REPLACE TEMP VIEW v_window AS
+	SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following
+	exclude ties) as sum_rows FROM generate_series(1, 10) i;
+
+SELECT * FROM v_window;
+
+SELECT pg_get_viewdef('v_window');
+
+CREATE OR REPLACE TEMP VIEW v_window AS
+	SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following
+	exclude no others) as sum_rows FROM generate_series(1, 10) i;
+
+SELECT * FROM v_window;
+
+SELECT pg_get_viewdef('v_window');
+
+CREATE OR REPLACE TEMP VIEW v_window AS
+	SELECT i, sum(i) over (order by i groups between 1 preceding and 1 following
+	exclude no others) as sum_rows FROM generate_series(1, 10) i;
+
+SELECT * FROM v_window;
+
+SELECT pg_get_viewdef('v_window');
+
+-- RANGE BETWEEN with values tests
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four desc range between 2::int8 preceding and 1::int2 preceding),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude no others),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude current row),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude group),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude ties),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 6::int2 following exclude ties),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 6::int2 following exclude group),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (partition by four order by unique1 range between 5::int8 preceding and 6::int2 following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (partition by four order by unique1 range between 5::int8 preceding and 6::int2 following
+	exclude current row),unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following),
+	salary, enroll_date from empsalary;
+
+select sum(salary) over (order by enroll_date desc range between '1 year'::interval preceding and '1 year'::interval following),
+	salary, enroll_date from empsalary;
+
+select sum(salary) over (order by enroll_date desc range between '1 year'::interval following and '1 year'::interval following),
+	salary, enroll_date from empsalary;
+
+select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following
+	exclude current row), salary, enroll_date from empsalary;
+
+select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following
+	exclude group), salary, enroll_date from empsalary;
+
+select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following
+	exclude ties), salary, enroll_date from empsalary;
+
+select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following),
+	salary, enroll_time from empsalary;
+
+select sum(salary) over (order by enroll_time desc range between '1 hour'::interval preceding and '2 hours'::interval following),
+	salary, enroll_time from empsalary;
+
+select sum(salary) over (order by enroll_time desc range between '1 hour'::interval following and '2 hours'::interval following),
+	salary, enroll_time from empsalary;
+
+select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following
+	exclude current row), salary, enroll_time from empsalary;
+
+select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following
+	exclude group), salary, enroll_time from empsalary;
+
+select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following
+	exclude ties), salary, enroll_time from empsalary;
+
+select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following),
+	salary, enroll_timetz from empsalary;
+
+select sum(salary) over (order by enroll_timetz desc range between '1 hour'::interval preceding and '2 hours'::interval following),
+	salary, enroll_timetz from empsalary;
+
+select sum(salary) over (order by enroll_timetz desc range between '1 hour'::interval following and '2 hours'::interval following),
+	salary, enroll_timetz from empsalary;
+
+select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following
+	exclude current row), salary, enroll_timetz from empsalary;
+
+select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following
+	exclude group), salary, enroll_timetz from empsalary;
+
+select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following
+	exclude ties), salary, enroll_timetz from empsalary;
+
+select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following),
+	salary, enroll_interval from empsalary;
+
+select sum(salary) over (order by enroll_interval desc range between '1 year'::interval preceding and '2 years'::interval following),
+	salary, enroll_interval from empsalary;
+
+select sum(salary) over (order by enroll_interval desc range between '1 year'::interval following and '2 years'::interval following),
+	salary, enroll_interval from empsalary;
+
+select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following
+	exclude current row), salary, enroll_interval from empsalary;
+
+select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following
+	exclude group), salary, enroll_interval from empsalary;
+
+select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following
+	exclude ties), salary, enroll_interval from empsalary;
+
+select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following),
+	salary, enroll_timestamptz from empsalary;
+
+select sum(salary) over (order by enroll_timestamptz desc range between '1 year'::interval preceding and '2 years'::interval following),
+	salary, enroll_timestamptz from empsalary;
+
+select sum(salary) over (order by enroll_timestamptz desc range between '1 year'::interval following and '2 years'::interval following),
+	salary, enroll_timestamptz from empsalary;
+
+select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following
+	exclude current row), salary, enroll_timestamptz from empsalary;
+
+select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following
+	exclude group), salary, enroll_timestamptz from empsalary;
+
+select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following
+	exclude ties), salary, enroll_timestamptz from empsalary;
+
+select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following),
+	salary, enroll_timestamp from empsalary;
+
+select sum(salary) over (order by enroll_timestamp desc range between '1 year'::interval preceding and '2 years'::interval following),
+	salary, enroll_timestamp from empsalary;
+
+select sum(salary) over (order by enroll_timestamp desc range between '1 year'::interval following and '2 years'::interval following),
+	salary, enroll_timestamp from empsalary;
+
+select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following
+	exclude current row), salary, enroll_timestamp from empsalary;
+
+select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following
+	exclude group), salary, enroll_timestamp from empsalary;
+
+select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following
+	exclude ties), salary, enroll_timestamp from empsalary;
+
+select sum(salary) over (order by enroll_timestamp range between current row and '2 years'::interval following),
+	salary, enroll_timestamp from empsalary;
+
+select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and current row),
+	salary, enroll_timestamp from empsalary;
+
+select sum(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following),
+	salary, enroll_date from empsalary;
+
+select sum(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following
+	exclude current row), salary, enroll_date from empsalary;
+
+select sum(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following
+	exclude group), salary, enroll_date from empsalary;
+
+select sum(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following
+	exclude ties), salary, enroll_date from empsalary;
+
+select first_value(salary) over(order by salary range between 1000 preceding and 1000 following),
+	lead(salary) over(order by salary range between 1000 preceding and 1000 following),
+	nth_value(salary, 1) over(order by salary range between 1000 preceding and 1000 following),
+	salary from empsalary;
+
+select last_value(salary) over(order by salary range between 1000 preceding and 1000 following),
+	lag(salary) over(order by salary range between 1000 preceding and 1000 following),
+	salary from empsalary;
+
+select first_value(salary) over(order by salary range between 1000 following and 3000 following
+	exclude current row),
+	lead(salary) over(order by salary range between 1000 following and 3000 following exclude ties),
+	nth_value(salary, 1) over(order by salary range between 1000 following and 3000 following
+	exclude ties),
+	salary from empsalary;
+
+select last_value(salary) over(order by salary range between 1000 following and 3000 following
+	exclude group),
+	lag(salary) over(order by salary range between 1000 following and 3000 following exclude group),
+	salary from empsalary;
+
+select first_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following
+	exclude ties),
+	last_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following),
+	salary, enroll_date from empsalary;
+
+-- RANGE BETWEEN with values negative tests
+select sum(salary) over (order by enroll_timestamp, enroll_date range between '1 year'::interval preceding and '2 years'::interval following
+	exclude ties), salary, enroll_timestamp from empsalary;
+
+select sum(salary) over (range between '1 year'::interval preceding and '2 years'::interval following
+	exclude ties), salary, enroll_timestamp from empsalary;
+
+select sum(salary) over (order by depname range between '1 year'::interval preceding and '2 years'::interval following
+	exclude ties), salary, enroll_timestamp from empsalary;
+
+select max(enroll_date) over (order by enroll_timestamp range between 1 preceding and 2 following
+	exclude ties), salary, enroll_timestamp from empsalary;
+
+select max(enroll_date) over (order by salary range between -1 preceding and 2 following
+	exclude ties), salary, enroll_timestamp from empsalary;
+
+select max(enroll_date) over (order by salary range between 1 preceding and -2 following
+	exclude ties), salary, enroll_timestamp from empsalary;
+
+select max(enroll_date) over (order by salary range between '1 year'::interval preceding and '2 years'::interval following
+	exclude ties), salary, enroll_timestamp from empsalary;
+
+-- GROUPS tests
+
+SELECT sum(unique1) over (order by four groups between unbounded preceding and current row),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between unbounded preceding and unbounded following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between current row and unbounded following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 1 preceding and unbounded following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 1 following and unbounded following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between unbounded preceding and 2 following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 preceding),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 0 preceding and 0 following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following
+	exclude current row), unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following
+	exclude group), unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following
+	exclude ties), unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (partition by ten
+	order by four groups between 0 preceding and 0 following),unique1, four, ten
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (partition by ten
+	order by four groups between 0 preceding and 0 following exclude current row), unique1, four, ten
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (partition by ten
+	order by four groups between 0 preceding and 0 following exclude group), unique1, four, ten
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (partition by ten
+	order by four groups between 0 preceding and 0 following exclude ties), unique1, four, ten
+FROM tenk1 WHERE unique1 < 10;
+
+select first_value(salary) over(order by enroll_date groups between 1 preceding and 1 following),
+	lead(salary) over(order by enroll_date groups between 1 preceding and 1 following),
+	nth_value(salary, 1) over(order by enroll_date groups between 1 preceding and 1 following),
+	salary, enroll_date from empsalary;
+
+select last_value(salary) over(order by enroll_date groups between 1 preceding and 1 following),
+	lag(salary) over(order by enroll_date groups between 1 preceding and 1 following),
+	salary, enroll_date from empsalary;
+
+select first_value(salary) over(order by enroll_date groups between 1 following and 3 following
+	exclude current row),
+	lead(salary) over(order by enroll_date groups between 1 following and 3 following exclude ties),
+	nth_value(salary, 1) over(order by enroll_date groups between 1 following and 3 following
+	exclude ties),
+	salary, enroll_date from empsalary;
+
+select last_value(salary) over(order by enroll_date groups between 1 following and 3 following
+	exclude group),
+	lag(salary) over(order by enroll_date groups between 1 following and 3 following exclude group),
+	salary, enroll_date from empsalary;
+
+-- Show differences in values mode between ROWS, RANGE, and GROUPS
+WITH cte (x) AS (
+        SELECT * FROM generate_series(1, 35, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x rows between 1 preceding and 1 following);
+
+WITH cte (x) AS (
+        SELECT * FROM generate_series(1, 35, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x range between 1 preceding and 1 following);
+
+WITH cte (x) AS (
+        SELECT * FROM generate_series(1, 35, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x groups between 1 preceding and 1 following);
+
+WITH cte (x) AS (
+        select 1 union all select 1 union all select 1 union all
+        SELECT * FROM generate_series(5, 49, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x rows between 1 preceding and 1 following);
+
+WITH cte (x) AS (
+        select 1 union all select 1 union all select 1 union all
+        SELECT * FROM generate_series(5, 49, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x range between 1 preceding and 1 following);
+
+WITH cte (x) AS (
+        select 1 union all select 1 union all select 1 union all
+        SELECT * FROM generate_series(5, 49, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x groups between 1 preceding and 1 following);
+
 -- with UNION
 SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk2)s LIMIT 0;
 
