rows_frame_types.20091114.patch

application/octet-stream

Filename: rows_frame_types.20091114.patch
Type: application/octet-stream
Part: 0
Message: add more frame types in window functions (ROWS)

Patch

Format: context
File+
src/backend/executor/nodeWindowAgg.c 402 0
src/backend/nodes/copyfuncs.c 20 0
src/backend/nodes/equalfuncs.c 15 0
src/backend/nodes/outfuncs.c 17 0
src/backend/nodes/readfuncs.c 1 0
src/backend/optimizer/plan/createplan.c 4 0
src/backend/optimizer/plan/planner.c 4 0
src/backend/parser/gram.y 85 0
src/backend/parser/parse_agg.c 2 0
src/backend/parser/parse_clause.c 2 0
src/backend/utils/adt/ruleutils.c 10 0
src/include/nodes/execnodes.h 7 0
src/include/nodes/nodes.h 1 0
src/include/nodes/parsenodes.h 18 0
src/include/nodes/plannodes.h 2 0
src/include/optimizer/planmain.h 2 0
src/test/regress/expected/window.out 138 0
src/test/regress/sql/window.sql 34 0
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
***************
*** 164,170 **** static void spool_tuples(WindowAggState *winstate, int64 pos);
  static void release_partition(WindowAggState *winstate);
  
  static bool row_is_in_frame(WindowAggState *winstate, int64 pos,
! 				TupleTableSlot *slot);
  static void update_frametailpos(WindowObject winobj, TupleTableSlot *slot);
  
  static WindowStatePerAggData *initialize_peragg(WindowAggState *winstate,
--- 164,171 ----
  static void release_partition(WindowAggState *winstate);
  
  static bool 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);
  
  static WindowStatePerAggData *initialize_peragg(WindowAggState *winstate,
***************
*** 391,396 **** eval_windowaggregates(WindowAggState *winstate)
--- 392,398 ----
  	MemoryContext oldContext;
  	ExprContext *econtext;
  	TupleTableSlot *agg_row_slot;
+ 	WindowObject agg_winobj;
  
  	numaggs = winstate->numaggs;
  	if (numaggs == 0)
***************
*** 398,405 **** eval_windowaggregates(WindowAggState *winstate)
--- 400,410 ----
  
  	/* final output execution is in ps_ExprContext */
  	econtext = winstate->ss.ps.ps_ExprContext;
+ 	agg_winobj = winstate->agg_winobj;
+ 	agg_row_slot = winstate->agg_row_slot;
  
  	/*
+ 	 * XXX:
  	 * Currently, we support only a subset of the SQL-standard window framing
  	 * rules.  In all the supported cases, the window frame always consists of
  	 * a contiguous group of rows extending forward from the start of the
***************
*** 434,466 **** eval_windowaggregates(WindowAggState *winstate)
  	 */
  
  	/*
! 	 * If we've already aggregated up through current row, reuse the saved
! 	 * result values.  NOTE: this test works for the currently supported
! 	 * framing rules, but will need fixing when more are added.
  	 */
! 	if (winstate->aggregatedupto > winstate->currentpos)
  	{
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
! 			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
  		}
- 		return;
  	}
  
! 	/* Initialize aggregates on first call for partition */
! 	if (winstate->currentpos == 0)
  	{
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
  		}
  	}
  
  	/*
--- 439,508 ----
  	 */
  
  	/*
! 	 * First, update frame positions. Aggregate framing detection is similar
! 	 * to one in window function APIs, but it optimizes it's own flow to
! 	 * detect frame head and tail, which finds the tail during aggregation
! 	 * rather than before starting. This results in tuplestore buffering
! 	 * be minimized in such simple frame like RANGE BETWEEN UNBOUNDED PRECEDING
! 	 * AND CURRENT ROW (ie default when specifying only ORDER BY clause.)
! 	 */
! 	update_frameheadpos(agg_winobj, winstate->agg_row_slot);
! 
! 	/*
! 	 * Initialize aggregates on first call for partition or when some rows
! 	 * were off from frame since the last call.
  	 */
! 	if (winstate->currentpos == 0 ||
! 		winstate->frameheadpos > winstate->aggregatedbase)
  	{
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
! 		}
! 
! 		winstate->aggregatedbase = winstate->frameheadpos;
! 
! 		/*
! 		 * When frame head is advancing, aggregate mark position should be
! 		 * updated so that tuplestore can discard unnecessary rows.
! 		 * When the condition is false (i.e. FRAMEOPTION_START_UNBOUNDED_PRECEDING),
! 		 * mark_ptr is invalid and we don't care about mark position.
! 		 */
! 		if (agg_winobj->markptr > 0)
! 		{
! 			if (winstate->frameheadpos < winstate->currentpos)
! 				WinSetMarkPosition(agg_winobj, winstate->frameheadpos - 1);
! 
! 			winstate->aggregatedupto = winstate->aggregatedbase;
! 			/*
! 			 * Discard current state, so that subsequent loop starts with null slot
! 			 */
! 			if (!TupIsNull(agg_row_slot))
! 				ExecClearTuple(agg_row_slot);
  		}
  	}
  
! 	/*
! 	 * If we've already aggregated up through current row and frame covers
! 	 * current row, reuse the saved result values. NOTE: this test should get
! 	 * more efficient in some framing modes.
! 	 */
! 	if (winstate->frametail_stable &&
! 		winstate->aggregatedbase <= winstate->currentpos &&
! 		winstate->aggregatedupto > winstate->currentpos)
  	{
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
! 			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
  		}
+ 		return;
  	}
  
  	/*
***************
*** 470,486 **** eval_windowaggregates(WindowAggState *winstate)
  	 * at position aggregatedupto.	The agg_ptr read pointer must always point
  	 * to the next row to read into agg_row_slot.
  	 */
- 	agg_row_slot = winstate->agg_row_slot;
  	for (;;)
  	{
  		/* Fetch next row if we didn't already */
  		if (TupIsNull(agg_row_slot))
  		{
! 			spool_tuples(winstate, winstate->aggregatedupto);
! 			tuplestore_select_read_pointer(winstate->buffer,
! 										   winstate->agg_ptr);
! 			if (!tuplestore_gettupleslot(winstate->buffer, true, true,
! 										 agg_row_slot))
  				break;			/* must be end of partition */
  		}
  
--- 512,530 ----
  	 * at position aggregatedupto.	The agg_ptr read pointer must always point
  	 * to the next row to read into agg_row_slot.
  	 */
  	for (;;)
  	{
  		/* Fetch next row if we didn't already */
  		if (TupIsNull(agg_row_slot))
  		{
! 			/*
! 			 * NOTE:
! 			 * Here window_gettupleslot() tends to fetch current row
! 			 * (but not always). We should optimize window_gettupleslot()
! 			 * on this case, where reads ahead and back currently.
! 			 */
! 			if(!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
! 									agg_row_slot))
  				break;			/* must be end of partition */
  		}
  
***************
*** 627,634 **** begin_partition(WindowAggState *winstate)
  	winstate->frametail_valid = false;
  	winstate->spooled_rows = 0;
  	winstate->currentpos = 0;
  	winstate->frametailpos = -1;
! 	winstate->aggregatedupto = 0;
  	ExecClearTuple(winstate->agg_row_slot);
  
  	/*
--- 671,679 ----
  	winstate->frametail_valid = false;
  	winstate->spooled_rows = 0;
  	winstate->currentpos = 0;
+ 	winstate->frameheadpos = -1;
  	winstate->frametailpos = -1;
! 
  	ExecClearTuple(winstate->agg_row_slot);
  
  	/*
***************
*** 665,671 **** begin_partition(WindowAggState *winstate)
  
  	/* create a read pointer for aggregates, if needed */
  	if (winstate->numaggs > 0)
! 		winstate->agg_ptr = tuplestore_alloc_read_pointer(winstate->buffer, 0);
  
  	/* create mark and read pointers for each real window function */
  	for (i = 0; i < numfuncs; i++)
--- 710,738 ----
  
  	/* create a read pointer for aggregates, if needed */
  	if (winstate->numaggs > 0)
! 	{
! 		WindowObject agg_winobj = winstate->agg_winobj;
! 		int			readptr_flag = 0;
! 		WindowAgg  *node = (WindowAgg *) winstate->ss.ps.plan;
! 		int			frameOptions = node->frameOptions;
! 
! 		/* create a read pointer for aggregate marking if the frame head may move */
! 		if (!(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING))
! 		{
! 			agg_winobj->markptr = tuplestore_alloc_read_pointer(winstate->buffer, 0);
! 			readptr_flag = EXEC_FLAG_BACKWARD;
! //			winstate->agg_base_ptr = tuplestore_alloc_read_pointer(winstate->buffer, 0);
! 		}
! 
! 		agg_winobj->readptr = tuplestore_alloc_read_pointer(winstate->buffer,
! 															readptr_flag);
! //		winstate->agg_ptr = tuplestore_alloc_read_pointer(winstate->buffer, 0);
! 
! 		agg_winobj->markpos = -1;
! 		agg_winobj->seekpos = -1;
! 		winstate->aggregatedbase = 0;
! 		winstate->aggregatedupto = 0;
! 	}
  
  	/* create mark and read pointers for each real window function */
  	for (i = 0; i < numfuncs; i++)
***************
*** 814,842 **** row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot)
  
  	Assert(pos >= 0);			/* else caller error */
  
! 	/* We only support frame start mode UNBOUNDED PRECEDING for now */
! 	Assert(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING);
  
  	/* In UNBOUNDED FOLLOWING mode, all partition rows are in frame */
  	if (frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
  		return true;
  
! 	/* Else frame tail mode must be CURRENT ROW */
! 	Assert(frameOptions & FRAMEOPTION_END_CURRENT_ROW);
  
! 	/* if row is current row or a predecessor, it must be in frame */
! 	if (pos <= winstate->currentpos)
! 		return true;
  
! 	/* In ROWS mode, *only* such rows are in frame */
! 	if (frameOptions & FRAMEOPTION_ROWS)
! 		return false;
  
! 	/* Else must be RANGE mode */
! 	Assert(frameOptions & FRAMEOPTION_RANGE);
  
! 	/* In frame iff it's a peer of current row */
! 	return are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot);
  }
  
  /*
--- 881,1073 ----
  
  	Assert(pos >= 0);			/* else caller error */
  
! 	if (frameOptions & FRAMEOPTION_START_CURRENT_ROW)
! 	{
! 		if (frameOptions & FRAMEOPTION_ROWS)
! 		{
! 			if (pos < winstate->currentpos)
! 				return false;
! 		}
! 		else
! 		{
! 			Assert(frameOptions & FRAMEOPTION_RANGE);
! 
! 			/* preceding row that is not peer is out of frame */
! 			if (pos < winstate->currentpos &&
! 				!are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
! 				return false;
! 		}
! 	}
! 
! 	if (frameOptions & FRAMEOPTION_START_VALUE_PRECEDING)
! 	{
! 		if (frameOptions & FRAMEOPTION_ROWS)
! 		{
! 			A_Const	   *offset = (A_Const *) node->startOffset;
! 
! 			Assert(offset != NULL && IsA(&(offset->val), Integer));
! 			if (pos < winstate->currentpos - intVal(&(offset->val)))
! 				return false;
! 		}
! 		else
! 			elog(ERROR, "RANGE BETWEEN n PRECEDING AND ... is not supported so far");
! 	}
! 
! 	if (frameOptions & FRAMEOPTION_START_VALUE_FOLLOWING)
! 	{
! 		if (frameOptions & FRAMEOPTION_ROWS)
! 		{
! 			A_Const	   *offset = (A_Const *) node->startOffset;
! 
! 			Assert(offset != NULL && IsA(&(offset->val), Integer));
! 			if (pos < winstate->currentpos + intVal(&(offset->val)))
! 				return false;
! 		}
! 		else
! 			elog(ERROR, "RANGE BETWEEN n FOLLOWING AND ... is not supported so far");
! 	}
! 
! 	/* seems ok for starting bound */
  
  	/* In UNBOUNDED FOLLOWING mode, all partition rows are in frame */
  	if (frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
  		return true;
  
! 	if (frameOptions & FRAMEOPTION_END_CURRENT_ROW)
! 	{
! 		if (frameOptions & FRAMEOPTION_ROWS)
! 		{
! 			return (pos <= winstate->currentpos);
! 		}
! 		else
! 		{
! 			Assert(frameOptions & FRAMEOPTION_RANGE);
  
! 			/* preceding row or peer is in frame */
! 			return pos <= winstate->currentpos ||
! 				are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot);
! 		}
! 	}
  
! 	if (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING)
! 	{
! 		if (frameOptions & FRAMEOPTION_ROWS)
! 		{
! 			A_Const	   *offset = (A_Const *) node->endOffset;
! 
! 			Assert(offset != NULL && IsA(&(offset->val), Integer));
! 			return (pos <= winstate->currentpos - intVal(&(offset->val)));
! 		}
! 		else
! 			elog(ERROR, "RANGE BETWEEN ... AND n PRECEDING is not supported so far");
! 	}
! 
! 	if (frameOptions & FRAMEOPTION_END_VALUE_FOLLOWING)
! 	{
! 		if (frameOptions & FRAMEOPTION_ROWS)
! 		{
! 			A_Const	   *offset = (A_Const *) node->endOffset;
! 
! 			Assert(offset != NULL && IsA(&(offset->val), Integer));
! 			return (pos <= winstate->currentpos + intVal(&(offset->val)));
! 		}
! 		else
! 			elog(ERROR, "RANGE BETWEEN ... AND n FOLLOWING is not supported so far");
! 	}
! 
! 	/* It is not supposed to reach here */
! 	elog(ERROR, "invalid frame type");
! 
! 	return false; /* keep compiler quiet */
! }
! 
! static void
! update_frameheadpos(WindowObject winobj, TupleTableSlot *slot)
! {
! 	WindowAggState *winstate = winobj->winstate;
! 	WindowAgg  *node = (WindowAgg *) winstate->ss.ps.plan;
! 	int			frameOptions = node->frameOptions;
! 
! 	if (winstate->framehead_valid)
! 		return;
! 
! 	if (frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
! 	{
! 		winstate->frameheadpos = 0;
! 		winstate->framehead_valid = true;
! 		return;
! 	}
  
! 	if (frameOptions & FRAMEOPTION_START_CURRENT_ROW)
! 	{
! 		int64		pos = winstate->currentpos;
  
! 		if (frameOptions & FRAMEOPTION_ROWS)
! 		{
! 			winstate->frameheadpos = pos;
! 			winstate->framehead_valid = true;
! 			return;
! 		}
! 		Assert(frameOptions & FRAMEOPTION_RANGE);
! 
! 		if (node->ordNumCols == 0)
! 		{
! 			winstate->frameheadpos = 0;
! 			winstate->framehead_valid = true;
! 			return;
! 		}
! 		/*
! 		 * In RANGE START_CURRENT mode, frame head is
! 		 * the head row of all peers.
! 		 */
! 		for(;;)
! 		{
! 			if (!window_gettupleslot(winobj, pos, slot))
! 				break;
! 			if (!are_peers(winstate, slot,
! 						   winstate->ss.ss_ScanTupleSlot))
! 				break;
! 			pos--;
! 		}
! 		winstate->frameheadpos = pos + 1;
! 		winstate->framehead_valid = true;
! 		return;
! 	}
! 
! 	if (frameOptions & FRAMEOPTION_START_VALUE_PRECEDING)
! 	{
! 		if (frameOptions & FRAMEOPTION_ROWS)
! 		{
! 			A_Const	   *offset = (A_Const *) node->startOffset;
! 
! 			Assert(offset != NULL && IsA(&(offset->val), Integer));
! 			winstate->frameheadpos = winstate->currentpos - intVal(&(offset->val));
! 			if (winstate->frameheadpos < 0)
! 				winstate->frameheadpos = 0;
! 			winstate->framehead_valid = true;
! 			return;
! 		}
! 		elog(ERROR, "RANGE BETWEEN n PRECEDING AND ... is not supported so far");
! 	}
! 
! 	if (frameOptions & FRAMEOPTION_START_VALUE_FOLLOWING)
! 	{
! 		if (frameOptions & FRAMEOPTION_ROWS)
! 		{
! 			A_Const	   *offset = (A_Const *) node->startOffset;
! 
! 			Assert(offset != NULL && IsA(&(offset->val), Integer));
! 			winstate->frameheadpos = winstate->currentpos + intVal(&(offset->val));
! 
! 			/* we have to know end of the partition to cut off overflow */
! 			spool_tuples(winstate, -1);
! 			if (winstate->frameheadpos >= winstate->spooled_rows)
! 				winstate->frameheadpos = winstate->spooled_rows - 1;
! 			winstate->framehead_valid = true;
! 			return;
! 		}
! 		elog(ERROR, "RANGE BETWEEN n FOLLOWING AND ... is not supported so far");
! 	}
  }
  
  /*
***************
*** 858,866 **** update_frametailpos(WindowObject winobj, TupleTableSlot *slot)
  	if (winstate->frametail_valid)
  		return;					/* already known for current row */
  
- 	/* We only support frame start mode UNBOUNDED PRECEDING for now */
- 	Assert(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING);
- 
  	/* In UNBOUNDED FOLLOWING mode, all partition rows are in frame */
  	if (frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
  	{
--- 1089,1094 ----
***************
*** 870,916 **** update_frametailpos(WindowObject winobj, TupleTableSlot *slot)
  		return;
  	}
  
! 	/* Else frame tail mode must be CURRENT ROW */
! 	Assert(frameOptions & FRAMEOPTION_END_CURRENT_ROW);
! 
! 	/* In ROWS mode, exactly the rows up to current are in frame */
! 	if (frameOptions & FRAMEOPTION_ROWS)
  	{
! 		winstate->frametailpos = winstate->currentpos;
! 		winstate->frametail_valid = true;
! 		return;
! 	}
  
! 	/* Else must be RANGE mode */
! 	Assert(frameOptions & FRAMEOPTION_RANGE);
  
! 	/* If no ORDER BY, all rows are peers with each other */
! 	if (node->ordNumCols == 0)
  	{
! 		spool_tuples(winstate, -1);
! 		winstate->frametailpos = winstate->spooled_rows - 1;
! 		winstate->frametail_valid = true;
! 		return;
  	}
  
! 	/*
! 	 * Else we have to search for the first non-peer of the current row. We
! 	 * assume the current value of frametailpos is a lower bound on the
! 	 * possible frame tail location, ie, frame tail never goes backward, and
! 	 * that currentpos is also a lower bound, ie, current row is always in
! 	 * frame.
! 	 */
! 	ftnext = Max(winstate->frametailpos, winstate->currentpos) + 1;
! 	for (;;)
  	{
! 		if (!window_gettupleslot(winobj, ftnext, slot))
! 			break;				/* end of partition */
! 		if (!are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
! 			break;				/* not peer of current row */
! 		ftnext++;
  	}
- 	winstate->frametailpos = ftnext - 1;
- 	winstate->frametail_valid = true;
  }
  
  
--- 1098,1185 ----
  		return;
  	}
  
! 	if (frameOptions & FRAMEOPTION_END_CURRENT_ROW)
  	{
! 		/* In ROWS mode, exactly the rows up to current are in frame */
! 		if (frameOptions & FRAMEOPTION_ROWS)
! 		{
! 			winstate->frametailpos = winstate->currentpos;
! 			winstate->frametail_valid = true;
! 			return;
! 		}
! 		else
! 		{
! 			/* Else must be RANGE mode */
! 			Assert(frameOptions & FRAMEOPTION_RANGE);
  
! 			/* If no ORDER BY, all rows are peers with each other */
! 			if (node->ordNumCols == 0)
! 			{
! 				spool_tuples(winstate, -1);
! 				winstate->frametailpos = winstate->spooled_rows - 1;
! 				winstate->frametail_valid = true;
! 				return;
! 			}
  
! 			/*
! 			 * Else we have to search for the first non-peer of the current row. We
! 			 * assume the current value of frametailpos is a lower bound on the
! 			 * possible frame tail location, ie, frame tail never goes backward, and
! 			 * that currentpos is also a lower bound, ie, current row is always in
! 			 * frame.
! 			 */
! 			ftnext = Max(winstate->frametailpos, winstate->currentpos) + 1;
! 			for (;;)
! 			{
! 				if (!window_gettupleslot(winobj, ftnext, slot))
! 					break;				/* end of partition */
! 				if (!are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
! 					break;				/* not peer of current row */
! 				ftnext++;
! 			}
! 			winstate->frametailpos = ftnext - 1;
! 			winstate->frametail_valid = true;
! 			return;
! 		}
! 	}
! 
! 	if (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING)
  	{
! 		/* In ROWS mode, frame boundary phisically n before current */
! 		if (frameOptions & FRAMEOPTION_ROWS)
! 		{
! 			A_Const	   *offset = (A_Const *) node->endOffset;
! 
! 			Assert(offset != NULL && IsA(&(offset->val), Integer));
! 			winstate->frametailpos = winstate->currentpos - intVal(&(offset->val));
! 			if(winstate->frametailpos < 0)
! 				winstate->frametailpos = 0;
! 			winstate->frametail_valid = true;
! 			return;
! 		}
! 		else
! 			elog(ERROR, "RANGE BETWEEN ... AND n PRECEDING is not supported for now");
  	}
  
! 	if (frameOptions & FRAMEOPTION_END_VALUE_FOLLOWING)
  	{
! 		if (frameOptions & FRAMEOPTION_ROWS)
! 		{
! 			A_Const	   *offset = (A_Const *) node->endOffset;
! 
! 			Assert(offset != NULL && IsA(&(offset->val), Integer));
! 			winstate->frametailpos = winstate->currentpos + intVal(&(offset->val));
! 
! 			/* we have to know end of the partition to cut off overflow */
! 			spool_tuples(winstate, -1);
! 			if (winstate->frametailpos >= winstate->spooled_rows)
! 				winstate->frametailpos = winstate->spooled_rows - 1;
! 			winstate->frametail_valid = true;
! 			return;
! 		}
! 		else
! 			elog(ERROR, "RANGE BETWEEN ... AND n FOLLOWING is not supported for now");
  	}
  }
  
  
***************
*** 964,969 **** restart:
--- 1233,1239 ----
  	{
  		/* Advance current row within partition */
  		winstate->currentpos++;
+ 		winstate->framehead_valid = false;
  		/* This might mean that the frame tail moves, too */
  		winstate->frametail_valid = false;
  	}
***************
*** 1264,1272 **** ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
--- 1534,1558 ----
  	winstate->numfuncs = wfuncno + 1;
  	winstate->numaggs = aggno + 1;
  
+ 	if (winstate->numaggs > 0)
+ 	{
+ 		WindowObject agg_winobj = makeNode(WindowObjectData);
+ 
+ 		agg_winobj->winstate = winstate;
+ 		agg_winobj->argstates = NULL;
+ 		agg_winobj->localmem = NULL;
+ 		/* make sure markptr = -1 to invalidate. it may not be used */
+ 		agg_winobj->markptr = -1;
+ 		agg_winobj->readptr = -1;
+ 		winstate->agg_winobj = agg_winobj;
+ 	}
+ 
  	winstate->partition_spooled = false;
  	winstate->more_partitions = false;
  
+ 	winstate->frametail_stable = !(node->frameOptions &
+ 								  (FRAMEOPTION_END_VALUE_PRECEDING |
+ 								   FRAMEOPTION_END_VALUE_FOLLOWING));
  	return winstate;
  }
  
***************
*** 1749,1754 **** WinGetFuncArgInPartition(WindowObject winobj, int argno,
--- 2035,2042 ----
  						 bool *isnull, bool *isout)
  {
  	WindowAggState *winstate;
+ 	WindowAgg  *node;
+ 	int			frameOptions;
  	ExprContext *econtext;
  	TupleTableSlot *slot;
  	bool		gottuple;
***************
*** 1756,1761 **** WinGetFuncArgInPartition(WindowObject winobj, int argno,
--- 2044,2051 ----
  
  	Assert(WindowObjectIsValid(winobj));
  	winstate = winobj->winstate;
+ 	node = (WindowAgg *) winstate->ss.ps.plan;
+ 	frameOptions = node->frameOptions;
  	econtext = winstate->ss.ps.ps_ExprContext;
  	slot = winstate->temp_slot_1;
  
***************
*** 1791,1797 **** WinGetFuncArgInPartition(WindowObject winobj, int argno,
  		if (isout)
  			*isout = false;
  		if (set_mark)
! 			WinSetMarkPosition(winobj, abs_pos);
  		econtext->ecxt_outertuple = slot;
  		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
  							econtext, isnull, NULL);
--- 2081,2103 ----
  		if (isout)
  			*isout = false;
  		if (set_mark)
! 		{
! 			int64	mark_pos = abs_pos;
! 
! 			/*
! 			 * In moving RANGE frame mode, we must keep mark at
! 			 * frameheadpos - 1, since that row is to be checked
! 			 * to find frame starting boundary.
! 			 */
! 			if (!(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) &&
! 				frameOptions & FRAMEOPTION_RANGE)
! 			{
! 				update_frameheadpos(winobj, winstate->temp_slot_2);
! 				if (mark_pos >= winstate->frameheadpos)
! 					mark_pos = winstate->frameheadpos - 1;
! 			}
! 			WinSetMarkPosition(winobj, mark_pos);
! 		}
  		econtext->ecxt_outertuple = slot;
  		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
  							econtext, isnull, NULL);
***************
*** 1822,1827 **** WinGetFuncArgInFrame(WindowObject winobj, int argno,
--- 2128,2135 ----
  					 bool *isnull, bool *isout)
  {
  	WindowAggState *winstate;
+ 	WindowAgg  *node;
+ 	int			frameOptions;
  	ExprContext *econtext;
  	TupleTableSlot *slot;
  	bool		gottuple;
***************
*** 1829,1834 **** WinGetFuncArgInFrame(WindowObject winobj, int argno,
--- 2137,2144 ----
  
  	Assert(WindowObjectIsValid(winobj));
  	winstate = winobj->winstate;
+ 	node = (WindowAgg *) winstate->ss.ps.plan;
+ 	frameOptions = node->frameOptions;
  	econtext = winstate->ss.ps.ps_ExprContext;
  	slot = winstate->temp_slot_1;
  
***************
*** 1838,1844 **** WinGetFuncArgInFrame(WindowObject winobj, int argno,
  			abs_pos = winstate->currentpos + relpos;
  			break;
  		case WINDOW_SEEK_HEAD:
! 			abs_pos = relpos;
  			break;
  		case WINDOW_SEEK_TAIL:
  			update_frametailpos(winobj, slot);
--- 2148,2155 ----
  			abs_pos = winstate->currentpos + relpos;
  			break;
  		case WINDOW_SEEK_HEAD:
! 			update_frameheadpos(winobj, slot);
! 			abs_pos = winstate->frameheadpos + relpos;
  			break;
  		case WINDOW_SEEK_TAIL:
  			update_frametailpos(winobj, slot);
***************
*** 1865,1872 **** WinGetFuncArgInFrame(WindowObject winobj, int argno,
  	{
  		if (isout)
  			*isout = false;
  		if (set_mark)
! 			WinSetMarkPosition(winobj, abs_pos);
  		econtext->ecxt_outertuple = slot;
  		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
  							econtext, isnull, NULL);
--- 2176,2200 ----
  	{
  		if (isout)
  			*isout = false;
+ 
  		if (set_mark)
! 		{
! 			int64		mark_pos = abs_pos;
! 
! 			/*
! 			 * In moving RANGE frame mode, we must keep mark at
! 			 * frameheadpos - 1, since that row is to be checked
! 			 * to find frame starting boundary.
! 			 */
! 			if (!(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) &&
! 				frameOptions & FRAMEOPTION_RANGE)
! 			{
! 				update_frameheadpos(winobj, winstate->temp_slot_2);
! 				if (mark_pos >= winstate->frameheadpos)
! 					mark_pos = winstate->frameheadpos - 1;
! 			}
! 			WinSetMarkPosition(winobj, mark_pos);
! 		}
  		econtext->ecxt_outertuple = slot;
  		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
  							econtext, isnull, NULL);
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 718,723 **** _copyWindowAgg(WindowAgg *from)
--- 718,725 ----
  		COPY_POINTER_FIELD(ordOperators, from->ordNumCols * sizeof(Oid));
  	}
  	COPY_SCALAR_FIELD(frameOptions);
+ 	COPY_NODE_FIELD(startOffset);
+ 	COPY_NODE_FIELD(endOffset);
  
  	return newnode;
  }
***************
*** 1844,1850 **** _copyWindowClause(WindowClause *from)
  	COPY_STRING_FIELD(refname);
  	COPY_NODE_FIELD(partitionClause);
  	COPY_NODE_FIELD(orderClause);
! 	COPY_SCALAR_FIELD(frameOptions);
  	COPY_SCALAR_FIELD(winref);
  	COPY_SCALAR_FIELD(copiedOrder);
  
--- 1846,1852 ----
  	COPY_STRING_FIELD(refname);
  	COPY_NODE_FIELD(partitionClause);
  	COPY_NODE_FIELD(orderClause);
! 	COPY_NODE_FIELD(frame);
  	COPY_SCALAR_FIELD(winref);
  	COPY_SCALAR_FIELD(copiedOrder);
  
***************
*** 2062,2067 **** _copySortBy(SortBy *from)
--- 2064,2082 ----
  	return newnode;
  }
  
+ static WindowFrameDef *
+ _copyWindowFrameDef(WindowFrameDef *from)
+ {
+ 	WindowFrameDef *newnode = makeNode(WindowFrameDef);
+ 
+ 	COPY_SCALAR_FIELD(options);
+ 	COPY_NODE_FIELD(startOffset);
+ 	COPY_NODE_FIELD(endOffset);
+ 	COPY_LOCATION_FIELD(location);
+ 
+ 	return newnode;
+ }
+ 
  static WindowDef *
  _copyWindowDef(WindowDef *from)
  {
***************
*** 2071,2077 **** _copyWindowDef(WindowDef *from)
  	COPY_STRING_FIELD(refname);
  	COPY_NODE_FIELD(partitionClause);
  	COPY_NODE_FIELD(orderClause);
! 	COPY_SCALAR_FIELD(frameOptions);
  	COPY_LOCATION_FIELD(location);
  
  	return newnode;
--- 2086,2092 ----
  	COPY_STRING_FIELD(refname);
  	COPY_NODE_FIELD(partitionClause);
  	COPY_NODE_FIELD(orderClause);
! 	COPY_NODE_FIELD(frame);
  	COPY_LOCATION_FIELD(location);
  
  	return newnode;
***************
*** 4154,4159 **** copyObject(void *from)
--- 4169,4177 ----
  		case T_SortBy:
  			retval = _copySortBy(from);
  			break;
+ 		case T_WindowFrameDef:
+ 			retval = _copyWindowFrameDef(from);
+ 			break;
  		case T_WindowDef:
  			retval = _copyWindowDef(from);
  			break;
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2035,2047 **** _equalSortBy(SortBy *a, SortBy *b)
  }
  
  static bool
  _equalWindowDef(WindowDef *a, WindowDef *b)
  {
  	COMPARE_STRING_FIELD(name);
  	COMPARE_STRING_FIELD(refname);
  	COMPARE_NODE_FIELD(partitionClause);
  	COMPARE_NODE_FIELD(orderClause);
! 	COMPARE_SCALAR_FIELD(frameOptions);
  	COMPARE_LOCATION_FIELD(location);
  
  	return true;
--- 2035,2057 ----
  }
  
  static bool
+ _equalWindowFrameDef(WindowFrameDef *a, WindowFrameDef *b)
+ {
+ 	COMPARE_SCALAR_FIELD(options);
+ 	COMPARE_NODE_FIELD(startOffset);
+ 	COMPARE_NODE_FIELD(endOffset);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalWindowDef(WindowDef *a, WindowDef *b)
  {
  	COMPARE_STRING_FIELD(name);
  	COMPARE_STRING_FIELD(refname);
  	COMPARE_NODE_FIELD(partitionClause);
  	COMPARE_NODE_FIELD(orderClause);
! 	COMPARE_NODE_FIELD(frame);
  	COMPARE_LOCATION_FIELD(location);
  
  	return true;
***************
*** 2186,2192 **** _equalWindowClause(WindowClause *a, WindowClause *b)
  	COMPARE_STRING_FIELD(refname);
  	COMPARE_NODE_FIELD(partitionClause);
  	COMPARE_NODE_FIELD(orderClause);
! 	COMPARE_SCALAR_FIELD(frameOptions);
  	COMPARE_SCALAR_FIELD(winref);
  	COMPARE_SCALAR_FIELD(copiedOrder);
  
--- 2196,2202 ----
  	COMPARE_STRING_FIELD(refname);
  	COMPARE_NODE_FIELD(partitionClause);
  	COMPARE_NODE_FIELD(orderClause);
! 	COMPARE_NODE_FIELD(frame);
  	COMPARE_SCALAR_FIELD(winref);
  	COMPARE_SCALAR_FIELD(copiedOrder);
  
***************
*** 2847,2852 **** equal(void *a, void *b)
--- 2857,2865 ----
  		case T_SortBy:
  			retval = _equalSortBy(a, b);
  			break;
+ 		case T_WindowFrameDef:
+ 			retval = _equalWindowFrameDef(a, b);
+ 			break;
  		case T_WindowDef:
  			retval = _equalWindowDef(a, b);
  			break;
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 610,615 **** _outWindowAgg(StringInfo str, WindowAgg *node)
--- 610,617 ----
  		appendStringInfo(str, " %u", node->ordOperators[i]);
  
  	WRITE_INT_FIELD(frameOptions);
+ 	WRITE_NODE_FIELD(startOffset);
+ 	WRITE_NODE_FIELD(endOffset);
  }
  
  static void
***************
*** 2024,2030 **** _outWindowClause(StringInfo str, WindowClause *node)
  	WRITE_STRING_FIELD(refname);
  	WRITE_NODE_FIELD(partitionClause);
  	WRITE_NODE_FIELD(orderClause);
! 	WRITE_INT_FIELD(frameOptions);
  	WRITE_UINT_FIELD(winref);
  	WRITE_BOOL_FIELD(copiedOrder);
  }
--- 2026,2032 ----
  	WRITE_STRING_FIELD(refname);
  	WRITE_NODE_FIELD(partitionClause);
  	WRITE_NODE_FIELD(orderClause);
! 	WRITE_NODE_FIELD(frame);
  	WRITE_UINT_FIELD(winref);
  	WRITE_BOOL_FIELD(copiedOrder);
  }
***************
*** 2307,2312 **** _outSortBy(StringInfo str, SortBy *node)
--- 2309,2324 ----
  }
  
  static void
+ _outWindowFrameDef(StringInfo str, WindowFrameDef *node)
+ {
+ 	WRITE_NODE_TYPE("WINDOWFRAMEDEF");
+ 
+ 	WRITE_INT_FIELD(options);
+ 	WRITE_NODE_FIELD(startOffset);
+ 	WRITE_NODE_FIELD(endOffset);
+ }
+ 
+ static void
  _outWindowDef(StringInfo str, WindowDef *node)
  {
  	WRITE_NODE_TYPE("WINDOWDEF");
***************
*** 2315,2321 **** _outWindowDef(StringInfo str, WindowDef *node)
  	WRITE_STRING_FIELD(refname);
  	WRITE_NODE_FIELD(partitionClause);
  	WRITE_NODE_FIELD(orderClause);
! 	WRITE_INT_FIELD(frameOptions);
  	WRITE_LOCATION_FIELD(location);
  }
  
--- 2327,2333 ----
  	WRITE_STRING_FIELD(refname);
  	WRITE_NODE_FIELD(partitionClause);
  	WRITE_NODE_FIELD(orderClause);
! 	WRITE_NODE_FIELD(frame);
  	WRITE_LOCATION_FIELD(location);
  }
  
***************
*** 2848,2853 **** _outNode(StringInfo str, void *obj)
--- 2860,2868 ----
  			case T_SortBy:
  				_outSortBy(str, obj);
  				break;
+ 			case T_WindowFrameDef:
+ 				_outWindowFrameDef(str, obj);
+ 				break;
  			case T_WindowDef:
  				_outWindowDef(str, obj);
  				break;
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 278,284 **** _readWindowClause(void)
  	READ_STRING_FIELD(refname);
  	READ_NODE_FIELD(partitionClause);
  	READ_NODE_FIELD(orderClause);
! 	READ_INT_FIELD(frameOptions);
  	READ_UINT_FIELD(winref);
  	READ_BOOL_FIELD(copiedOrder);
  
--- 278,284 ----
  	READ_STRING_FIELD(refname);
  	READ_NODE_FIELD(partitionClause);
  	READ_NODE_FIELD(orderClause);
! 	READ_NODE_FIELD(frame);
  	READ_UINT_FIELD(winref);
  	READ_BOOL_FIELD(copiedOrder);
  
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 3360,3366 **** make_windowagg(PlannerInfo *root, List *tlist,
  			   int numWindowFuncs, Index winref,
  			   int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
  			   int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators,
! 			   int frameOptions, Plan *lefttree)
  {
  	WindowAgg  *node = makeNode(WindowAgg);
  	Plan	   *plan = &node->plan;
--- 3360,3367 ----
  			   int numWindowFuncs, Index winref,
  			   int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
  			   int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators,
! 			   int frameOptions, Node *startOffset, Node *endOffset,
! 			   Plan *lefttree)
  {
  	WindowAgg  *node = makeNode(WindowAgg);
  	Plan	   *plan = &node->plan;
***************
*** 3375,3380 **** make_windowagg(PlannerInfo *root, List *tlist,
--- 3376,3383 ----
  	node->ordColIdx = ordColIdx;
  	node->ordOperators = ordOperators;
  	node->frameOptions = frameOptions;
+ 	node->startOffset = startOffset;
+ 	node->endOffset = endOffset;
  
  	copy_plan_costsize(plan, lefttree); /* only care about copying size */
  	cost_windowagg(&windowagg_path, root,
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 1484,1490 **** grouping_planner(PlannerInfo *root, double tuple_fraction)
  				result_plan = (Plan *)
  					make_windowagg(root,
  								   (List *) copyObject(window_tlist),
! 							   list_length(wflists->windowFuncs[wc->winref]),
  								   wc->winref,
  								   partNumCols,
  								   partColIdx,
--- 1484,1490 ----
  				result_plan = (Plan *)
  					make_windowagg(root,
  								   (List *) copyObject(window_tlist),
! 								   list_length(wflists->windowFuncs[wc->winref]),
  								   wc->winref,
  								   partNumCols,
  								   partColIdx,
***************
*** 1492,1498 **** grouping_planner(PlannerInfo *root, double tuple_fraction)
  								   ordNumCols,
  								   ordColIdx,
  								   ordOperators,
! 								   wc->frameOptions,
  								   result_plan);
  			}
  		}
--- 1492,1500 ----
  								   ordNumCols,
  								   ordColIdx,
  								   ordOperators,
! 								   wc->frame->options,
! 								   wc->frame->startOffset,
! 								   wc->frame->endOffset,
  								   result_plan);
  			}
  		}
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 428,434 **** static TypeName *TableFuncTypeName(List *columns);
  %type <list>	window_clause window_definition_list opt_partition_clause
  %type <windef>	window_definition over_clause window_specification
  %type <str>		opt_existing_window_name
! %type <ival>	opt_frame_clause frame_extent frame_bound
  
  
  /*
--- 428,434 ----
  %type <list>	window_clause window_definition_list opt_partition_clause
  %type <windef>	window_definition over_clause window_specification
  %type <str>		opt_existing_window_name
! %type <node>	opt_frame_clause frame_extent frame_bound
  
  
  /*
***************
*** 9687,9697 **** over_clause: OVER window_specification
  			| OVER ColId
  				{
  					WindowDef *n = makeNode(WindowDef);
  					n->name = $2;
  					n->refname = NULL;
  					n->partitionClause = NIL;
  					n->orderClause = NIL;
! 					n->frameOptions = FRAMEOPTION_DEFAULTS;
  					n->location = @2;
  					$$ = n;
  				}
--- 9687,9701 ----
  			| OVER ColId
  				{
  					WindowDef *n = makeNode(WindowDef);
+ 					WindowFrameDef *frame = makeNode(WindowFrameDef);
+ 					frame->options = FRAMEOPTION_DEFAULTS;
+ 					frame->startOffset = NULL;
+ 					frame->endOffset = NULL;
  					n->name = $2;
  					n->refname = NULL;
  					n->partitionClause = NIL;
  					n->orderClause = NIL;
! 					n->frame = frame;
  					n->location = @2;
  					$$ = n;
  				}
***************
*** 9707,9713 **** window_specification: '(' opt_existing_window_name opt_partition_clause
  					n->refname = $2;
  					n->partitionClause = $3;
  					n->orderClause = $4;
! 					n->frameOptions = $5;
  					n->location = @1;
  					$$ = n;
  				}
--- 9711,9717 ----
  					n->refname = $2;
  					n->partitionClause = $3;
  					n->orderClause = $4;
! 					n->frame = (WindowFrameDef *) $5;
  					n->location = @1;
  					$$ = n;
  				}
***************
*** 9739,9789 **** opt_partition_clause: PARTITION BY expr_list		{ $$ = $3; }
  opt_frame_clause:
  			RANGE frame_extent
  				{
! 					$$ = FRAMEOPTION_NONDEFAULT | FRAMEOPTION_RANGE | $2;
  				}
  			| ROWS frame_extent
  				{
! 					$$ = FRAMEOPTION_NONDEFAULT | FRAMEOPTION_ROWS | $2;
  				}
  			| /*EMPTY*/
! 				{ $$ = FRAMEOPTION_DEFAULTS; }
  		;
  
  frame_extent: frame_bound
  				{
  					/* reject invalid cases */
! 					if ($1 & FRAMEOPTION_START_UNBOUNDED_FOLLOWING)
  						ereport(ERROR,
  								(errcode(ERRCODE_WINDOWING_ERROR),
  								 errmsg("frame start cannot be UNBOUNDED FOLLOWING"),
  								 parser_errposition(@1)));
! 					if ($1 & FRAMEOPTION_START_CURRENT_ROW)
! 						ereport(ERROR,
! 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 								 errmsg("frame start at CURRENT ROW is not implemented"),
! 								 parser_errposition(@1)));
! 					$$ = $1 | FRAMEOPTION_END_CURRENT_ROW;
  				}
  			| BETWEEN frame_bound AND frame_bound
  				{
  					/* reject invalid cases */
! 					if ($2 & FRAMEOPTION_START_UNBOUNDED_FOLLOWING)
  						ereport(ERROR,
  								(errcode(ERRCODE_WINDOWING_ERROR),
  								 errmsg("frame start cannot be UNBOUNDED FOLLOWING"),
  								 parser_errposition(@2)));
! 					if ($2 & FRAMEOPTION_START_CURRENT_ROW)
! 						ereport(ERROR,
! 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 								 errmsg("frame start at CURRENT ROW is not implemented"),
! 								 parser_errposition(@2)));
! 					if ($4 & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
  						ereport(ERROR,
  								(errcode(ERRCODE_WINDOWING_ERROR),
  								 errmsg("frame end cannot be UNBOUNDED PRECEDING"),
  								 parser_errposition(@4)));
  					/* shift converts START_ options to END_ options */
! 					$$ = FRAMEOPTION_BETWEEN | $2 | ($4 << 1);
  				}
  		;
  
--- 9743,9826 ----
  opt_frame_clause:
  			RANGE frame_extent
  				{
! 					WindowFrameDef *frame = (WindowFrameDef *) $2;
! 					frame->options |= FRAMEOPTION_NONDEFAULT | FRAMEOPTION_RANGE;
! 					frame->location = @1;
! 					$$ = (Node *) frame;
  				}
  			| ROWS frame_extent
  				{
! 					WindowFrameDef *frame = (WindowFrameDef *) $2;
! 					frame->options |= FRAMEOPTION_NONDEFAULT | FRAMEOPTION_ROWS;
! 					frame->location = @1;
! 					$$ = (Node *) frame;
  				}
  			| /*EMPTY*/
! 				{
! 					WindowFrameDef *frame = makeNode(WindowFrameDef);
! 					frame->options = FRAMEOPTION_DEFAULTS;
! 					frame->startOffset = NULL;
! 					frame->endOffset = NULL;
! 					frame->location = -1;
! 					$$ = (Node *) frame;
! 				}
  		;
  
  frame_extent: frame_bound
  				{
+ 					WindowFrameDef *frame = (WindowFrameDef *) $1;
  					/* reject invalid cases */
! 					if (frame->options & FRAMEOPTION_START_UNBOUNDED_FOLLOWING)
  						ereport(ERROR,
  								(errcode(ERRCODE_WINDOWING_ERROR),
  								 errmsg("frame start cannot be UNBOUNDED FOLLOWING"),
  								 parser_errposition(@1)));
! 					frame->options |= FRAMEOPTION_END_CURRENT_ROW;
! 					$$ = (Node *) frame;
  				}
  			| BETWEEN frame_bound AND frame_bound
  				{
+ 					WindowFrameDef *frame1 = (WindowFrameDef *) $2;
+ 					WindowFrameDef *frame2 = (WindowFrameDef *) $4;
  					/* reject invalid cases */
! 					if (frame1->options & FRAMEOPTION_START_UNBOUNDED_FOLLOWING)
  						ereport(ERROR,
  								(errcode(ERRCODE_WINDOWING_ERROR),
  								 errmsg("frame start cannot be UNBOUNDED FOLLOWING"),
  								 parser_errposition(@2)));
! 					if (frame2->options & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
  						ereport(ERROR,
  								(errcode(ERRCODE_WINDOWING_ERROR),
  								 errmsg("frame end cannot be UNBOUNDED PRECEDING"),
  								 parser_errposition(@4)));
+ 					if (frame1->options & FRAMEOPTION_START_CURRENT_ROW &&
+ 						frame2->options &
+ 							(FRAMEOPTION_START_UNBOUNDED_PRECEDING |
+ 							 FRAMEOPTION_START_VALUE_PRECEDING))
+ 						ereport(ERROR,
+ 								(errcode(ERRCODE_WINDOWING_ERROR),
+ 								 errmsg("frame from current row cannot have preceding rows"),
+ 								 parser_errposition(@4)));
+ 					if (frame1->options & FRAMEOPTION_START_VALUE_FOLLOWING &&
+ 						frame2->options &
+ 							(FRAMEOPTION_START_VALUE_PRECEDING |
+ 							 FRAMEOPTION_START_CURRENT_ROW))
+ 						ereport(ERROR,
+ 								(errcode(ERRCODE_WINDOWING_ERROR),
+ 								 errmsg("frame from following row cannot have preceding rows"),
+ 								 parser_errposition(@4)));
+ 					if (frame1->options & FRAMEOPTION_START_VALUE_FOLLOWING &&
+ 						frame2->options & FRAMEOPTION_END_VALUE_PRECEDING)
+ 						ereport(ERROR,
+ 								(errcode(ERRCODE_WINDOWING_ERROR),
+ 								 errmsg("frame cannot be BETWEEN x FOLLOWING AND y PRECEDING"),
+ 								 parser_errposition(@1)));
  					/* shift converts START_ options to END_ options */
! 					frame1->options |= frame2->options << 1;
! 					frame1->endOffset = frame2->startOffset;
! 					/* frame2 is not necessary anymore */
! 					pfree(frame2);
! 					$$ = (Node *) frame1;
  				}
  		;
  
***************
*** 9795,9809 **** frame_extent: frame_bound
  frame_bound:
  			UNBOUNDED PRECEDING
  				{
! 					$$ = FRAMEOPTION_START_UNBOUNDED_PRECEDING;
  				}
  			| UNBOUNDED FOLLOWING
  				{
! 					$$ = FRAMEOPTION_START_UNBOUNDED_FOLLOWING;
  				}
  			| CURRENT_P ROW
  				{
! 					$$ = FRAMEOPTION_START_CURRENT_ROW;
  				}
  		;
  
--- 9832,9870 ----
  frame_bound:
  			UNBOUNDED PRECEDING
  				{
! 					WindowFrameDef *frame = makeNode(WindowFrameDef);
! 					frame->options = FRAMEOPTION_START_UNBOUNDED_PRECEDING;
! 					frame->startOffset = NULL;
! 					frame->endOffset = NULL;
! 					$$ = (Node *) frame;
  				}
  			| UNBOUNDED FOLLOWING
  				{
! 					WindowFrameDef *frame = makeNode(WindowFrameDef);
! 					frame->options = FRAMEOPTION_START_UNBOUNDED_FOLLOWING;
! 					frame->startOffset = NULL;
! 					frame->endOffset = NULL;
! 					$$ = (Node *) frame;
  				}
  			| CURRENT_P ROW
  				{
! 					WindowFrameDef *frame = makeNode(WindowFrameDef);
! 					frame->options = FRAMEOPTION_START_CURRENT_ROW;
! 					$$ = (Node *) frame;
! 				}
! 			| Iconst PRECEDING
! 				{
! 					WindowFrameDef *frame = makeNode(WindowFrameDef);
! 					frame->options = FRAMEOPTION_START_VALUE_PRECEDING;
! 					frame->startOffset = makeIntConst($1, @1);
! 					$$ = (Node *) frame;
! 				}
! 			| Iconst FOLLOWING
! 				{
! 					WindowFrameDef *frame = makeNode(WindowFrameDef);
! 					frame->options = FRAMEOPTION_START_VALUE_FOLLOWING;
! 					frame->startOffset = makeIntConst($1, @1);
! 					$$ = (Node *) frame;
  				}
  		;
  
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
***************
*** 136,142 **** transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
  		Assert(windef->refname == NULL &&
  			   windef->partitionClause == NIL &&
  			   windef->orderClause == NIL &&
! 			   windef->frameOptions == FRAMEOPTION_DEFAULTS);
  
  		foreach(lc, pstate->p_windowdefs)
  		{
--- 136,142 ----
  		Assert(windef->refname == NULL &&
  			   windef->partitionClause == NIL &&
  			   windef->orderClause == NIL &&
! 			   windef->frame->options == FRAMEOPTION_DEFAULTS);
  
  		foreach(lc, pstate->p_windowdefs)
  		{
***************
*** 174,180 **** transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
  				continue;
  			if (equal(refwin->partitionClause, windef->partitionClause) &&
  				equal(refwin->orderClause, windef->orderClause) &&
! 				refwin->frameOptions == windef->frameOptions)
  			{
  				/* found a duplicate window specification */
  				wfunc->winref = winref;
--- 174,180 ----
  				continue;
  			if (equal(refwin->partitionClause, windef->partitionClause) &&
  				equal(refwin->orderClause, windef->orderClause) &&
! 				equal(refwin->frame, windef->frame))
  			{
  				/* found a duplicate window specification */
  				wfunc->winref = winref;
*** a/src/backend/parser/parse_clause.c
--- b/src/backend/parser/parse_clause.c
***************
*** 1657,1669 **** transformWindowDefinitions(ParseState *pstate,
  			wc->orderClause = orderClause;
  			wc->copiedOrder = false;
  		}
! 		if (refwc && refwc->frameOptions != FRAMEOPTION_DEFAULTS)
  			ereport(ERROR,
  					(errcode(ERRCODE_WINDOWING_ERROR),
  					 errmsg("cannot override frame clause of window \"%s\"",
  							windef->refname),
  					 parser_errposition(pstate, windef->location)));
! 		wc->frameOptions = windef->frameOptions;
  		wc->winref = winref;
  
  		result = lappend(result, wc);
--- 1657,1669 ----
  			wc->orderClause = orderClause;
  			wc->copiedOrder = false;
  		}
! 		if (refwc && refwc->frame->options != FRAMEOPTION_DEFAULTS)
  			ereport(ERROR,
  					(errcode(ERRCODE_WINDOWING_ERROR),
  					 errmsg("cannot override frame clause of window \"%s\"",
  							windef->refname),
  					 parser_errposition(pstate, windef->location)));
! 		wc->frame = copyObject(windef->frame);
  		wc->winref = winref;
  
  		result = lappend(result, wc);
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 2985,2990 **** get_rule_windowspec(WindowClause *wc, List *targetList,
--- 2985,2991 ----
  {
  	StringInfo	buf = context->buf;
  	bool		needspace = false;
+ 	WindowFrameDef *frame = wc->frame;
  	const char *sep;
  	ListCell   *l;
  
***************
*** 3022,3051 **** get_rule_windowspec(WindowClause *wc, List *targetList,
  		needspace = true;
  	}
  	/* framing clause is never inherited, so print unless it's default */
! 	if (wc->frameOptions & FRAMEOPTION_NONDEFAULT)
  	{
  		if (needspace)
  			appendStringInfoChar(buf, ' ');
! 		if (wc->frameOptions & FRAMEOPTION_RANGE)
  			appendStringInfoString(buf, "RANGE ");
! 		else if (wc->frameOptions & FRAMEOPTION_ROWS)
  			appendStringInfoString(buf, "ROWS ");
  		else
  			Assert(false);
! 		if (wc->frameOptions & FRAMEOPTION_BETWEEN)
  			appendStringInfoString(buf, "BETWEEN ");
! 		if (wc->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
  			appendStringInfoString(buf, "UNBOUNDED PRECEDING ");
! 		else if (wc->frameOptions & FRAMEOPTION_START_CURRENT_ROW)
  			appendStringInfoString(buf, "CURRENT ROW ");
  		else
  			Assert(false);
! 		if (wc->frameOptions & FRAMEOPTION_BETWEEN)
  		{
  			appendStringInfoString(buf, "AND ");
! 			if (wc->frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
  				appendStringInfoString(buf, "UNBOUNDED FOLLOWING ");
! 			else if (wc->frameOptions & FRAMEOPTION_END_CURRENT_ROW)
  				appendStringInfoString(buf, "CURRENT ROW ");
  			else
  				Assert(false);
--- 3023,3052 ----
  		needspace = true;
  	}
  	/* framing clause is never inherited, so print unless it's default */
! 	if (frame->options & FRAMEOPTION_NONDEFAULT)
  	{
  		if (needspace)
  			appendStringInfoChar(buf, ' ');
! 		if (frame->options & FRAMEOPTION_RANGE)
  			appendStringInfoString(buf, "RANGE ");
! 		else if (frame->options & FRAMEOPTION_ROWS)
  			appendStringInfoString(buf, "ROWS ");
  		else
  			Assert(false);
! 		if (frame->options & FRAMEOPTION_BETWEEN)
  			appendStringInfoString(buf, "BETWEEN ");
! 		if (frame->options & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
  			appendStringInfoString(buf, "UNBOUNDED PRECEDING ");
! 		else if (frame->options & FRAMEOPTION_START_CURRENT_ROW)
  			appendStringInfoString(buf, "CURRENT ROW ");
  		else
  			Assert(false);
! 		if (frame->options & FRAMEOPTION_BETWEEN)
  		{
  			appendStringInfoString(buf, "AND ");
! 			if (frame->options & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
  				appendStringInfoString(buf, "UNBOUNDED FOLLOWING ");
! 			else if (frame->options & FRAMEOPTION_END_CURRENT_ROW)
  				appendStringInfoString(buf, "CURRENT ROW ");
  			else
  				Assert(false);
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 14,19 ****
--- 14,20 ----
  #ifndef EXECNODES_H
  #define EXECNODES_H
  
+ #include "windowapi.h"
  #include "access/genam.h"
  #include "access/heapam.h"
  #include "access/skey.h"
***************
*** 1585,1594 **** typedef struct WindowAggState
  	FmgrInfo   *ordEqfunctions; /* equality funcs for ordering columns */
  	Tuplestorestate *buffer;	/* stores rows of current partition */
  	int			current_ptr;	/* read pointer # for current */
- 	int			agg_ptr;		/* read pointer # for aggregates */
  	int64		spooled_rows;	/* total # of rows in buffer */
  	int64		currentpos;		/* position of current row in partition */
  	int64		frametailpos;	/* current frame tail position */
  	int64		aggregatedupto; /* rows before this one are aggregated */
  
  	MemoryContext wincontext;	/* context for partition-lifespan data */
--- 1586,1597 ----
  	FmgrInfo   *ordEqfunctions; /* equality funcs for ordering columns */
  	Tuplestorestate *buffer;	/* stores rows of current partition */
  	int			current_ptr;	/* read pointer # for current */
  	int64		spooled_rows;	/* total # of rows in buffer */
  	int64		currentpos;		/* position of current row in partition */
+ 	int64		frameheadpos;	/* current frame head position */
  	int64		frametailpos;	/* current frame tail position */
+ 	WindowObject agg_winobj;
+ 	int64		aggregatedbase; /* row position this aggregate started on */
  	int64		aggregatedupto; /* rows before this one are aggregated */
  
  	MemoryContext wincontext;	/* context for partition-lifespan data */
***************
*** 1600,1607 **** typedef struct WindowAggState
--- 1603,1613 ----
  										 * tuplestore */
  	bool		more_partitions;/* true if there's more partitions after this
  								 * one */
+ 	bool		framehead_valid;/* true if frameheadpos is known up to date
+ 								 * for current row */
  	bool		frametail_valid;/* true if frametailpos is known up to date
  								 * for current row */
+ 	bool		frametail_stable;	/* frame's tail doesn't move */
  
  	TupleTableSlot *first_part_slot;	/* first tuple of current or next
  										 * partition */
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 362,367 **** typedef enum NodeTag
--- 362,368 ----
  	T_ResTarget,
  	T_TypeCast,
  	T_SortBy,
+ 	T_WindowFrameDef,
  	T_WindowDef,
  	T_RangeSubselect,
  	T_RangeFunction,
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 376,381 **** typedef struct SortBy
--- 376,393 ----
  } SortBy;
  
  /*
+  * WindowFrameDef - for FRAME clause description in WINDOW definition
+  */
+ typedef struct WindowFrameDef
+ {
+ 	NodeTag		type;
+ 	int			options;		/* frame_clause options, see below */
+ 	Node	   *startOffset;	/* how far is the starting bound */
+ 	Node	   *endOffset;		/* how far is the ending bound */
+ 	int			location;		/* parse location, or -1 if none/unknown */
+ } WindowFrameDef;
+ 
+ /*
   * WindowDef - raw representation of WINDOW and OVER clauses
   *
   * For entries in a WINDOW list, "name" is the window name being defined.
***************
*** 390,396 **** typedef struct WindowDef
  	char	   *refname;		/* referenced window name, if any */
  	List	   *partitionClause;	/* PARTITION BY expression list */
  	List	   *orderClause;	/* ORDER BY (list of SortBy) */
! 	int			frameOptions;	/* frame_clause options, see below */
  	int			location;		/* parse location, or -1 if none/unknown */
  } WindowDef;
  
--- 402,408 ----
  	char	   *refname;		/* referenced window name, if any */
  	List	   *partitionClause;	/* PARTITION BY expression list */
  	List	   *orderClause;	/* ORDER BY (list of SortBy) */
! 	WindowFrameDef *frame;		/* FRAME clause including boundary values */
  	int			location;		/* parse location, or -1 if none/unknown */
  } WindowDef;
  
***************
*** 412,417 **** typedef struct WindowDef
--- 424,433 ----
  #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		0x01000 /* start is V. P. */
+ #define FRAMEOPTION_END_VALUE_PRECEDING			0x02000 /* end is V.P. */
+ #define FRAMEOPTION_START_VALUE_FOLLOWING		0x04000 /* start is V. F. */
+ #define FRAMEOPTION_END_VALUE_FOLLOWING			0x08000 /* end is V. F. */
  
  #define FRAMEOPTION_DEFAULTS \
  	(FRAMEOPTION_RANGE | FRAMEOPTION_START_UNBOUNDED_PRECEDING | \
***************
*** 794,800 **** typedef struct WindowClause
  	char	   *refname;		/* referenced window name, if any */
  	List	   *partitionClause;	/* PARTITION BY list */
  	List	   *orderClause;	/* ORDER BY list */
! 	int			frameOptions;	/* frame_clause options, see WindowDef */
  	Index		winref;			/* ID referenced by window functions */
  	bool		copiedOrder;	/* did we copy orderClause from refname? */
  } WindowClause;
--- 810,816 ----
  	char	   *refname;		/* referenced window name, if any */
  	List	   *partitionClause;	/* PARTITION BY list */
  	List	   *orderClause;	/* ORDER BY list */
! 	WindowFrameDef *frame;		/* frame_clause options and boundaries */
  	Index		winref;			/* ID referenced by window functions */
  	bool		copiedOrder;	/* did we copy orderClause from refname? */
  } WindowClause;
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 552,557 **** typedef struct WindowAgg
--- 552,559 ----
  	AttrNumber *ordColIdx;		/* their indexes in the target list */
  	Oid		   *ordOperators;	/* equality operators for ordering columns */
  	int			frameOptions;	/* frame_clause options, see WindowDef */
+ 	Node	   *startOffset;	/* frame start boundary */
+ 	Node	   *endOffset;		/* frame end boundary */
  } WindowAgg;
  
  /* ----------------
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
***************
*** 61,67 **** extern WindowAgg *make_windowagg(PlannerInfo *root, List *tlist,
  			   int numWindowFuncs, Index winref,
  			   int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
  			   int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators,
! 			   int frameOptions, Plan *lefttree);
  extern Group *make_group(PlannerInfo *root, List *tlist, List *qual,
  		   int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
  		   double numGroups,
--- 61,68 ----
  			   int numWindowFuncs, Index winref,
  			   int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
  			   int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators,
! 			   int frameOptions, Node *startOffset, Node *endOffset,
! 			   Plan *lefttree);
  extern Group *make_group(PlannerInfo *root, List *tlist, List *qual,
  		   int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
  		   double numGroups,
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
***************
*** 728,733 **** FROM (select distinct ten, four from tenk1) ss;
--- 728,871 ----
      3 |   2 |   4 |          2
  (20 rows)
  
+ SELECT sum(unique1) over (order by four range 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 (rows between current row and unbounded following),
+ 	unique1, four
+ FROM tenk1 WHERE unique1 < 10;
+  sum | unique1 | four 
+ -----+---------+------
+   45 |       4 |    0
+   41 |       2 |    2
+   39 |       1 |    1
+   38 |       6 |    2
+   32 |       9 |    1
+   23 |       8 |    0
+   15 |       5 |    1
+   10 |       3 |    3
+    7 |       7 |    3
+    0 |       0 |    0
+ (10 rows)
+ 
+ SELECT sum(unique1) over (rows between 2 preceding and 2 following),
+ 	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 1 preceding),
+ 	unique1, four
+ FROM tenk1 WHERE unique1 < 10;
+  sum | unique1 | four 
+ -----+---------+------
+      |       4 |    0
+    4 |       2 |    2
+    6 |       1 |    1
+    3 |       6 |    2
+    7 |       9 |    1
+   15 |       8 |    0
+   17 |       5 |    1
+   13 |       3 |    3
+    8 |       7 |    3
+   10 |       0 |    0
+ (10 rows)
+ 
+ SELECT sum(unique1) over (rows between 1 following and 3 following),
+ 	unique1, four
+ FROM tenk1 WHERE unique1 < 10;
+  sum | unique1 | four 
+ -----+---------+------
+    9 |       4 |    0
+   16 |       2 |    2
+   23 |       1 |    1
+   22 |       6 |    2
+   16 |       9 |    1
+   15 |       8 |    0
+   10 |       5 |    1
+    7 |       3 |    3
+    0 |       7 |    3
+    0 |       0 |    0
+ (10 rows)
+ 
+ SELECT sum(unique1) over (rows between unbounded preceding and 1 following),
+ 	unique1, four
+ FROM tenk1 WHERE unique1 < 10;
+  sum | unique1 | four 
+ -----+---------+------
+    6 |       4 |    0
+    7 |       2 |    2
+   13 |       1 |    1
+   22 |       6 |    2
+   30 |       9 |    1
+   35 |       8 |    0
+   38 |       5 |    1
+   45 |       3 |    3
+   45 |       7 |    3
+   45 |       0 |    0
+ (10 rows)
+ 
+ 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);
+  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 first_value(unique1) over w,
+ 	nth_value(unique1, 2) over w AS nth_2,
+ 	last_value(unique1) over w, unique1, four
+ FROM tenk1 WHERE unique1 < 10
+ WINDOW w AS (order by four range between current row and unbounded following);
+  first_value | nth_2 | last_value | unique1 | four 
+ -------------+-------+------------+---------+------
+            0 |     8 |          7 |       0 |    0
+            0 |     8 |          7 |       8 |    0
+            0 |     8 |          7 |       4 |    0
+            5 |     9 |          7 |       5 |    1
+            5 |     9 |          7 |       9 |    1
+            5 |     9 |          7 |       1 |    1
+            6 |     2 |          7 |       6 |    2
+            6 |     2 |          7 |       2 |    2
+            3 |     7 |          7 |       3 |    3
+            3 |     7 |          7 |       7 |    3
+ (10 rows)
+ 
  -- with UNION
  SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk2)s LIMIT 0;
   count 
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
***************
*** 161,166 **** SELECT four, ten/4 as two,
--- 161,200 ----
  	last_value(ten/4) over (partition by four order by ten/4 rows between unbounded preceding and current row)
  FROM (select distinct ten, four from tenk1) ss;
  
+ SELECT sum(unique1) over (order by four range between current row and unbounded following),
+ 	unique1, four
+ FROM tenk1 WHERE unique1 < 10;
+ 
+ SELECT sum(unique1) over (rows between current row and unbounded following),
+ 	unique1, four
+ FROM tenk1 WHERE unique1 < 10;
+ 
+ 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 1 preceding),
+ 	unique1, four
+ FROM tenk1 WHERE unique1 < 10;
+ 
+ SELECT sum(unique1) over (rows between 1 following and 3 following),
+ 	unique1, four
+ FROM tenk1 WHERE unique1 < 10;
+ 
+ SELECT sum(unique1) over (rows between unbounded preceding and 1 following),
+ 	unique1, four
+ FROM tenk1 WHERE unique1 < 10;
+ 
+ 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);
+ 
+ SELECT first_value(unique1) over w,
+ 	nth_value(unique1, 2) over w AS nth_2,
+ 	last_value(unique1) over w, unique1, four
+ FROM tenk1 WHERE unique1 < 10
+ WINDOW w AS (order by four range between current row and unbounded following);
+ 
  -- with UNION
  SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk2)s LIMIT 0;