diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index fe6dc8a..b7fc5f2 100644
*** a/src/backend/catalog/pg_aggregate.c
--- b/src/backend/catalog/pg_aggregate.c
*************** AggregateCreate(const char *aggName,
*** 56,61 ****
--- 56,62 ----
  				List *parameterDefaults,
  				Oid variadicArgType,
  				List *aggtransfnName,
+ 				List *agginvtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
*************** AggregateCreate(const char *aggName,
*** 68,78 ****
--- 69,81 ----
  	Datum		values[Natts_pg_aggregate];
  	Form_pg_proc proc;
  	Oid			transfn;
+ 	Oid			invtransfn = InvalidOid; /* can be omitted */
  	Oid			finalfn = InvalidOid;	/* can be omitted */
  	Oid			sortop = InvalidOid;	/* can be omitted */
  	Oid		   *aggArgTypes = parameterTypes->values;
  	bool		hasPolyArg;
  	bool		hasInternalArg;
+ 	bool		transIsStrict;
  	Oid			rettype;
  	Oid			finaltype;
  	Oid			fnArgs[FUNC_MAX_ARGS];
*************** AggregateCreate(const char *aggName,
*** 234,241 ****
--- 237,297 ----
  					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  					 errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
  	}
+ 
+ 	/*
+ 	 * Remember if trans function is strict as we need to validate this
+ 	 * later if when we're dealing with the inverse transition function
+ 	 */
+ 	transIsStrict = proc->proisstrict;
+ 
  	ReleaseSysCache(tup);
  
+ 	/* handle invtransfn, if supplied */
+ 	if (agginvtransfnName)
+ 	{
+ 		/*
+ 		 * This must have the same number of arguments with the same types as
+ 		 * the transition function. We can just borrow the argument details
+ 		 * from the transition function and try to find a function with
+ 		 * the name of the inverse transition function and with a signature
+ 		 * that matches the transition function's.
+ 		 */
+ 		invtransfn = lookup_agg_function(agginvtransfnName,
+ 					nargs_transfn, fnArgs, InvalidOid, &rettype);
+ 
+ 		/*
+ 		 * Ensure the return type of the inverse transition function matches
+ 		 * the expected type.
+ 		 */
+ 		if (rettype != aggTransType)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						errmsg("return type of inverse transition function %s is not %s",
+ 							NameListToString(agginvtransfnName),
+ 							format_type_be(aggTransType))));
+ 
+ 		tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(invtransfn));
+ 		if (!HeapTupleIsValid(tup))
+ 			elog(ERROR, "cache lookup failed for function %u", invtransfn);
+ 		proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 		/*
+ 		 * We force the strictness settings of the forward and inverse
+ 		 * transition functions to agree. This allows places which only need
+ 		 * forward transitions to not look at the inverse transition function
+ 		 * at all.
+ 		 */
+ 		if (transIsStrict != proc->proisstrict)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 						errmsg("strictness of forward and inverse transition functions must match"
+ 						)));
+ 		}
+ 		ReleaseSysCache(tup);
+ 
+ 	}
+ 
  	/* handle finalfn, if supplied */
  	if (aggfinalfnName)
  	{
*************** AggregateCreate(const char *aggName,
*** 391,396 ****
--- 447,453 ----
  	values[Anum_pg_aggregate_aggkind - 1] = CharGetDatum(aggKind);
  	values[Anum_pg_aggregate_aggnumdirectargs - 1] = Int16GetDatum(numDirectArgs);
  	values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
+ 	values[Anum_pg_aggregate_agginvtransfn - 1] = ObjectIdGetDatum(invtransfn);
  	values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
  	values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
  	values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
*************** AggregateCreate(const char *aggName,
*** 425,430 ****
--- 482,496 ----
  	referenced.objectSubId = 0;
  	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  
+ 	/* Depends on inverse transition function, if any */
+ 	if (OidIsValid(invtransfn))
+ 	{
+ 		referenced.classId = ProcedureRelationId;
+ 		referenced.objectId = invtransfn;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	/* Depends on final function, if any */
  	if (OidIsValid(finalfn))
  	{
*************** AggregateCreate(const char *aggName,
*** 447,453 ****
  }
  
  /*
!  * lookup_agg_function -- common code for finding both transfn and finalfn
   */
  static Oid
  lookup_agg_function(List *fnName,
--- 513,520 ----
  }
  
  /*
!  * lookup_agg_function
!  * common code for finding transfn, invtransfn and finalfn
   */
  static Oid
  lookup_agg_function(List *fnName,
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 640e19c..c0ea1f7 100644
*** a/src/backend/commands/aggregatecmds.c
--- b/src/backend/commands/aggregatecmds.c
*************** DefineAggregate(List *name, List *args, 
*** 60,65 ****
--- 60,66 ----
  	AclResult	aclresult;
  	char		aggKind = AGGKIND_NORMAL;
  	List	   *transfuncName = NIL;
+ 	List	   *invtransfuncName = NIL;
  	List	   *finalfuncName = NIL;
  	List	   *sortoperatorName = NIL;
  	TypeName   *baseType = NULL;
*************** DefineAggregate(List *name, List *args, 
*** 112,117 ****
--- 113,120 ----
  			transfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "sfunc1") == 0)
  			transfuncName = defGetQualifiedName(defel);
+ 		else if (pg_strcasecmp(defel->defname, "invsfunc") == 0)
+ 			invtransfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "finalfunc") == 0)
  			finalfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "sortop") == 0)
*************** DefineAggregate(List *name, List *args, 
*** 283,288 ****
--- 286,292 ----
  						   parameterDefaults,
  						   variadicArgType,
  						   transfuncName,		/* step function name */
+ 						   invtransfuncName,	/* inverse trans function name */
  						   finalfuncName,		/* final function name */
  						   sortoperatorName,	/* sort operator name */
  						   transTypeId, /* transition data type */
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 08f3167..beee43d 100644
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
*************** static void show_sort_group_keys(PlanSta
*** 85,90 ****
--- 85,91 ----
  					 List *ancestors, ExplainState *es);
  static void show_sort_info(SortState *sortstate, ExplainState *es);
  static void show_hash_info(HashState *hashstate, ExplainState *es);
+ static void show_windowagg_info(PlanState *planstate, ExplainState *es);
  static void show_tidbitmap_info(BitmapHeapScanState *planstate,
  								ExplainState *es);
  static void show_instrumentation_count(const char *qlabel, int which,
*************** ExplainNode(PlanState *planstate, List *
*** 1420,1425 ****
--- 1421,1430 ----
  		case T_Hash:
  			show_hash_info((HashState *) planstate, es);
  			break;
+ 		case T_WindowAgg:
+ 			if (es->verbose && planstate->instrument)
+ 				show_windowagg_info(planstate, es);
+ 			break;
  		default:
  			break;
  	}
*************** show_hash_info(HashState *hashstate, Exp
*** 1918,1923 ****
--- 1923,1943 ----
  	}
  }
  
+ static void
+ show_windowagg_info(PlanState *planstate, ExplainState *es)
+ {
+ 	WindowAggState *winaggstate = (WindowAggState *) planstate;
+ 	Instrumentation *inst = planstate->instrument;
+ 
+ 	if (inst->nloops > 0 && inst->ntuples > 0 && winaggstate->numaggs > 0)
+ 	{
+ 		double tperrow = winaggstate->aggfwdtrans /
+ 			(inst->nloops * inst->ntuples);
+ 
+ 		ExplainPropertyFloat("Transitions Per Row", tperrow, 1, es);
+ 	}
+ }
+ 
  /*
   * If it's EXPLAIN ANALYZE, show exact/lossy pages for a BitmapHeapScan node
   */
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 7e4bca5..9a7ed93 100644
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1798,1805 ****
--- 1798,1807 ----
  								aggref->aggtype,
  								aggref->inputcollid,
  								transfn_oid,
+ 								InvalidOid, /* invtrans is not needed here */
  								finalfn_oid,
  								&transfnexpr,
+ 								NULL,
  								&finalfnexpr);
  
  		/* set up infrastructure for calling the transfn and finalfn */
*************** ExecReScanAgg(AggState *node)
*** 2127,2168 ****
  }
  
  /*
-  * AggCheckCallContext - test if a SQL function is being called as an aggregate
-  *
-  * The transition and/or final functions of an aggregate may want to verify
-  * that they are being called as aggregates, rather than as plain SQL
-  * functions.  They should use this function to do so.	The return value
-  * is nonzero if being called as an aggregate, or zero if not.	(Specific
-  * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
-  * values could conceivably appear in future.)
-  *
-  * If aggcontext isn't NULL, the function also stores at *aggcontext the
-  * identity of the memory context that aggregate transition values are
-  * being stored in.
-  */
- int
- AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
- {
- 	if (fcinfo->context && IsA(fcinfo->context, AggState))
- 	{
- 		if (aggcontext)
- 			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
- 		return AGG_CONTEXT_AGGREGATE;
- 	}
- 	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
- 	{
- 		if (aggcontext)
- 			*aggcontext = ((WindowAggState *) fcinfo->context)->aggcontext;
- 		return AGG_CONTEXT_WINDOW;
- 	}
- 
- 	/* this is just to prevent "uninitialized variable" warnings */
- 	if (aggcontext)
- 		*aggcontext = NULL;
- 	return 0;
- }
- 
- /*
   * AggGetAggref - allow an aggregate support function to get its Aggref
   *
   * If the function is being called as an aggregate support function,
--- 2129,2134 ----
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 0b558e5..2b64175 100644
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
*************** typedef struct WindowStatePerFuncData
*** 102,119 ****
   */
  typedef struct WindowStatePerAggData
  {
! 	/* Oids of transfer functions */
  	Oid			transfn_oid;
  	Oid			finalfn_oid;	/* may be InvalidOid */
  
  	/*
! 	 * fmgr lookup data for transfer functions --- only valid when
  	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
  	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
  	FmgrInfo	finalfn;
  
  	/*
  	 * initial value from pg_aggregate entry
  	 */
--- 102,125 ----
   */
  typedef struct WindowStatePerAggData
  {
! 	/* Oids of transition functions */
  	Oid			transfn_oid;
+ 	Oid			invtransfn_oid; /* may be InvalidOid */
  	Oid			finalfn_oid;	/* may be InvalidOid */
  
  	/*
! 	 * fmgr lookup data for transition functions --- only valid when
  	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
  	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
+ 	FmgrInfo	invtransfn;
  	FmgrInfo	finalfn;
  
+ 	/* Aggregate properties */
+ 	bool		use_invtransfn;			/* whether to use the invtransfn */
+ 	bool		aggcontext_is_shared;	/* aggcontext is winstate's aggcontext */
+ 
  	/*
  	 * initial value from pg_aggregate entry
  	 */
*************** typedef struct WindowStatePerAggData
*** 140,149 ****
  	int			wfuncno;		/* index of associated PerFuncData */
  
  	/* Current transition value */
! 	Datum		transValue;		/* current transition value */
! 	bool		transValueIsNull;
  
! 	bool		noTransValue;	/* true if transValue not set yet */
  } WindowStatePerAggData;
  
  static void initialize_windowaggregate(WindowAggState *winstate,
--- 146,158 ----
  	int			wfuncno;		/* index of associated PerFuncData */
  
  	/* Current transition value */
! 	MemoryContext	aggcontext;			/* context for transValue */
! 	int64			transValueCount;	/* Number of aggregated values*/
! 	Datum			transValue;			/* current transition value */
! 	bool			transValueIsNull;
  
! 	/* Data local to eval_windowaggregates() */
! 	bool			restart;			/* tmp marker that agg needs restart */
  } WindowStatePerAggData;
  
  static void initialize_windowaggregate(WindowAggState *winstate,
*************** static void initialize_windowaggregate(W
*** 152,157 ****
--- 161,169 ----
  static void advance_windowaggregate(WindowAggState *winstate,
  						WindowStatePerFunc perfuncstate,
  						WindowStatePerAgg peraggstate);
+ static bool retreat_windowaggregate(WindowAggState *winstate,
+ 						WindowStatePerFunc perfuncstate,
+ 						WindowStatePerAgg peraggstate);
  static void finalize_windowaggregate(WindowAggState *winstate,
  						 WindowStatePerFunc perfuncstate,
  						 WindowStatePerAgg peraggstate,
*************** static bool are_peers(WindowAggState *wi
*** 181,186 ****
--- 193,245 ----
  static bool window_gettupleslot(WindowObject winobj, int64 pos,
  					TupleTableSlot *slot);
  
+ /*
+  * AggCheckCallContext - test if a SQL function is being called as an aggregate
+  *
+  * The transition and/or final functions of an aggregate may want to verify
+  * that they are being called as aggregates, rather than as plain SQL
+  * functions.  They should use this function to do so.	The return value
+  * is nonzero if being called as an aggregate, or zero if not.	(Specific
+  * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
+  * values could conceivably appear in future.)
+  *
+  * If aggcontext isn't NULL, the function also stores at *aggcontext the
+  * identity of the memory context that aggregate transition values are
+  * being stored in.
+  *
+  * This must live here, not in nodeAgg.c, because WindowStatePerAggData
+  * is private.
+  *
+  * Note that this function is only meant to be used by aggregate support
+  * functions, NOT by true window functions.
+  */
+ int
+ AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggState))
+ 	{
+ 		if (aggcontext)
+ 			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
+ 		return AGG_CONTEXT_AGGREGATE;
+ 	}
+ 	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
+ 	{
+ 		if (aggcontext)
+ 		{
+ 			/* Must lookup per-aggregate context */
+ 			WindowAggState *winstate = (WindowAggState *) fcinfo->context;
+ 			int				aggno = winstate->calledaggno;
+ 			Assert(0 <= aggno && aggno < winstate->numaggs);
+ 			*aggcontext = winstate->peragg[aggno].aggcontext;
+ 		}
+ 		return AGG_CONTEXT_WINDOW;
+ 	}
+ 
+ 	/* this is just to prevent "uninitialized variable" warnings */
+ 	if (aggcontext)
+ 		*aggcontext = NULL;
+ 	return 0;
+ }
  
  /*
   * initialize_windowaggregate
*************** initialize_windowaggregate(WindowAggStat
*** 193,210 ****
  {
  	MemoryContext oldContext;
  
  	if (peraggstate->initValueIsNull)
! 		peraggstate->transValue = peraggstate->initValue;
  	else
  	{
! 		oldContext = MemoryContextSwitchTo(winstate->aggcontext);
  		peraggstate->transValue = datumCopy(peraggstate->initValue,
  											peraggstate->transtypeByVal,
  											peraggstate->transtypeLen);
  		MemoryContextSwitchTo(oldContext);
  	}
  	peraggstate->transValueIsNull = peraggstate->initValueIsNull;
! 	peraggstate->noTransValue = peraggstate->initValueIsNull;
  	peraggstate->resultValueIsNull = true;
  }
  
--- 252,277 ----
  {
  	MemoryContext oldContext;
  
+ 	/* If we're using a private aggcontext, we may reset it here. But if the
+ 	 * context is shared, we don't know which other aggregates may still need
+ 	 * it, so we must leave it to the caller to reset at an appropriate time
+ 	 */
+ 	if (!peraggstate->aggcontext_is_shared)
+ 		MemoryContextResetAndDeleteChildren(peraggstate->aggcontext);
+ 
  	if (peraggstate->initValueIsNull)
! 		peraggstate->transValue = (Datum) 0;
  	else
  	{
! 		oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
  		peraggstate->transValue = datumCopy(peraggstate->initValue,
  											peraggstate->transtypeByVal,
  											peraggstate->transtypeLen);
  		MemoryContextSwitchTo(oldContext);
  	}
+ 	peraggstate->transValueCount = 0;
  	peraggstate->transValueIsNull = peraggstate->initValueIsNull;
! 	peraggstate->resultValue = (Datum) 0;
  	peraggstate->resultValueIsNull = true;
  }
  
*************** advance_windowaggregate(WindowAggState *
*** 256,265 ****
  
  	if (peraggstate->transfn.fn_strict)
  	{
! 		/*
! 		 * For a strict transfn, nothing happens when there's a NULL input; we
! 		 * just keep the prior transValue.
! 		 */
  		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
--- 323,329 ----
  
  	if (peraggstate->transfn.fn_strict)
  	{
! 		/* Skip NULL inputs for aggregates which desire that. */
  		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
*************** advance_windowaggregate(WindowAggState *
*** 268,308 ****
  				return;
  			}
  		}
! 		if (peraggstate->noTransValue)
  		{
! 			/*
! 			 * transValue has not been initialized. This is the first non-NULL
! 			 * input value. We use it as the initial value for transValue. (We
! 			 * already checked that the agg's input type is binary-compatible
! 			 * with its transtype, so straight copy here is OK.)
! 			 *
! 			 * We must copy the datum into aggcontext if it is pass-by-ref. We
! 			 * do not need to pfree the old transValue, since it's NULL.
! 			 */
! 			MemoryContextSwitchTo(winstate->aggcontext);
  			peraggstate->transValue = datumCopy(fcinfo->arg[1],
  												peraggstate->transtypeByVal,
  												peraggstate->transtypeLen);
  			peraggstate->transValueIsNull = false;
! 			peraggstate->noTransValue = false;
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
  		if (peraggstate->transValueIsNull)
  		{
- 			/*
- 			 * Don't call a strict function with NULL inputs.  Note it is
- 			 * possible to get here despite the above tests, if the transfn is
- 			 * strict *and* returned a NULL on a prior cycle. If that happens
- 			 * we will propagate the NULL all the way to the end.
- 			 */
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
  	}
  
  	/*
  	 * OK to call the transition function
  	 */
  	InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
  							 numArguments + 1,
--- 332,388 ----
  				return;
  			}
  		}
! 
! 		/*
! 		 * For strict transfer functions with initial value NULL we use the
! 		 * first non-NULL input as the initial state. (We already checked that
! 		 * the agg's input type is binary-compatible with its transtype, so
! 		 * straight copy here is OK.)
! 		 *
! 		 * We must copy the datum into aggcontext if it is pass-by-ref. We do
! 		 * not need to pfree the old transValue, since it's NULL.
! 		 */
! 		if (peraggstate->transValueCount == 0 && peraggstate->transValueIsNull)
  		{
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
  			peraggstate->transValue = datumCopy(fcinfo->arg[1],
  												peraggstate->transtypeByVal,
  												peraggstate->transtypeLen);
  			peraggstate->transValueIsNull = false;
! 			peraggstate->transValueCount = 1;
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
+ 
+ 		/*
+ 		 * Don't call a strict function with NULL inputs.  Note it is possible
+ 		 * to get here despite the above tests, if the transfn is strict *and*
+ 		 * returned a NULL on a prior cycle. If that happens we will propagate
+ 		 * the NULL all the way to the end. That can only happen if there's no
+ 		 * inverse transition function, though, since we disallow transitions
+ 		 * back to NULL if there is one below.
+ 		 */
  		if (peraggstate->transValueIsNull)
  		{
  			MemoryContextSwitchTo(oldContext);
+ 			Assert(peraggstate->invtransfn_oid == InvalidOid);
  			return;
  		}
  	}
  
  	/*
+ 	 * We must track the number of inputs that we add to transValue, since
+ 	 * to remove the last input, retreat_windowaggregate() musn't call the
+ 	 * inverse transition function, but simply reset transValue back to its
+ 	 * initial value.
+ 	 */
+ 	Assert(peraggstate->transValueCount >= 0);
+ 	peraggstate->transValueCount++;
+ 
+ 	/*
  	 * OK to call the transition function
+ 	 * Transfer functions with an inverse MUST not return NULL, see
+ 	 * retreat_windowaggregate()
  	 */
  	InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
  							 numArguments + 1,
*************** advance_windowaggregate(WindowAggState *
*** 310,316 ****
--- 390,404 ----
  							 (void *) winstate, NULL);
  	fcinfo->arg[0] = peraggstate->transValue;
  	fcinfo->argnull[0] = peraggstate->transValueIsNull;
+ 	winstate->calledaggno = perfuncstate->aggno;
  	newVal = FunctionCallInvoke(fcinfo);
+ 	winstate->calledaggno = -1;
+ 	if (peraggstate->invtransfn_oid != InvalidOid && fcinfo->isnull)
+ 	{
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ 				 errmsg("transition function with an inverse returned NULL")));
+ 	}
  
  	/*
  	 * If pass-by-ref datatype, must copy the new value into aggcontext and
*************** advance_windowaggregate(WindowAggState *
*** 322,328 ****
  	{
  		if (!fcinfo->isnull)
  		{
! 			MemoryContextSwitchTo(winstate->aggcontext);
  			newVal = datumCopy(newVal,
  							   peraggstate->transtypeByVal,
  							   peraggstate->transtypeLen);
--- 410,416 ----
  	{
  		if (!fcinfo->isnull)
  		{
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
  			newVal = datumCopy(newVal,
  							   peraggstate->transtypeByVal,
  							   peraggstate->transtypeLen);
*************** advance_windowaggregate(WindowAggState *
*** 337,342 ****
--- 425,571 ----
  }
  
  /*
+  * retreat_windowaggregate
+  * removes tuples from aggregation.
+  * The calling function must ensure that each aggregate has
+  * a valid inverse transition function.
+  */
+ static bool
+ retreat_windowaggregate(WindowAggState *winstate,
+ 						WindowStatePerFunc perfuncstate,
+ 						WindowStatePerAgg peraggstate)
+ {
+ 	WindowFuncExprState *wfuncstate = perfuncstate->wfuncstate;
+ 	int			numArguments = perfuncstate->numArguments;
+ 	FunctionCallInfoData fcinfodata;
+ 	FunctionCallInfo fcinfo = &fcinfodata;
+ 	Datum		newVal;
+ 	ListCell   *arg;
+ 	int			i;
+ 	MemoryContext oldContext;
+ 	ExprContext *econtext = winstate->tmpcontext;
+ 	ExprState  *filter = wfuncstate->aggfilter;
+ 
+ 	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ 
+ 	/* Skip anything FILTERed out */
+ 	if (filter)
+ 	{
+ 		bool		isnull;
+ 		Datum		res = ExecEvalExpr(filter, econtext, &isnull, NULL);
+ 
+ 		if (isnull || !DatumGetBool(res))
+ 		{
+ 			MemoryContextSwitchTo(oldContext);
+ 			return true;
+ 		}
+ 	}
+ 
+ 	/* We start from 1, since the 0th arg will be the transition value */
+ 	i = 1;
+ 	foreach(arg, wfuncstate->args)
+ 	{
+ 		ExprState  *argstate = (ExprState *) lfirst(arg);
+ 
+ 		fcinfo->arg[i] = ExecEvalExpr(argstate, econtext,
+ 									  &fcinfo->argnull[i], NULL);
+ 		i++;
+ 	}
+ 
+ 	/* Skip inputs containing NULLS for aggregates that require this */
+ 	if (peraggstate->invtransfn.fn_strict)
+ 	{
+ 		for (i = 1; i <= numArguments; i++)
+ 		{
+ 			if (fcinfo->argnull[i])
+ 			{
+ 				MemoryContextSwitchTo(oldContext);
+ 				return true;
+ 			}
+ 		}
+ 	}
+ 
+ 	/* There should still be an added but not yet removed value */
+ 	Assert(peraggstate->transValueCount >= 1);
+ 
+ 	/*
+ 	 * We mustn't use the inverse transition function to remove the last
+ 	 * input. Doing so would yield a non-NULL state, whereas we should be
+ 	 * in the initial state afterwards which may very well be NULL. So
+ 	 * instead, we simply re-initialize the aggregation in this case.
+ 	 */
+ 	if (peraggstate->transValueCount == 1)
+ 	{
+ 		MemoryContextSwitchTo(oldContext);
+ 		initialize_windowaggregate(winstate,
+ 								&winstate->perfunc[peraggstate->wfuncno],
+ 								peraggstate);
+ 		return true;
+ 	}
+ 
+ 	/*
+ 	 * Perform the inverse transition.
+ 	 *
+ 	 * For pairs of forward and inverse transition functions, the state may
+ 	 * never be NULL, except in the ignore_nulls case, and then only until
+ 	 * until we see the first non-NULL input during which time should never
+ 	 * attempt to invoke the inverse transition function. Excluding NULL
+ 	 * as a possible state value allows us to make it mean "sorry, can't
+ 	 * do an inverse transition in this case" when returned by the inverse
+ 	 * transition function. In that case, we report the failure to the
+ 	 * caller.
+ 	 */
+ 	if (peraggstate->transValueIsNull)
+ 	{
+ 		MemoryContextSwitchTo(oldContext);
+ 		elog(ERROR, "transition value is NULL during inverse transition");
+ 	}
+ 	InitFunctionCallInfoData(*fcinfo, &(peraggstate->invtransfn),
+ 							 numArguments + 1,
+ 							 perfuncstate->winCollation,
+ 							 (void *) winstate, NULL);
+ 	fcinfo->arg[0] = peraggstate->transValue;
+ 	fcinfo->argnull[0] = peraggstate->transValueIsNull;
+ 	winstate->calledaggno = perfuncstate->aggno;
+ 	newVal = FunctionCallInvoke(fcinfo);
+ 	winstate->calledaggno = -1;
+ 	if (fcinfo->isnull)
+ 	{
+ 		MemoryContextSwitchTo(oldContext);
+ 		return false;
+ 	}
+ 
+ 	/* Update number of added but not yet removed values */
+ 	peraggstate->transValueCount--;
+ 
+ 	/*
+ 	 * If pass-by-ref datatype, must copy the new value into aggcontext and
+ 	 * pfree the prior transValue.	But if invtransfn returned a pointer to its
+ 	 * first input, we don't need to do anything.
+ 	 */
+ 	if (!peraggstate->transtypeByVal &&
+ 		DatumGetPointer(newVal) != DatumGetPointer(peraggstate->transValue))
+ 	{
+ 		if (!fcinfo->isnull)
+ 		{
+ 			MemoryContextSwitchTo(peraggstate->aggcontext);
+ 			newVal = datumCopy(newVal,
+ 							   peraggstate->transtypeByVal,
+ 							   peraggstate->transtypeLen);
+ 		}
+ 		if (!peraggstate->transValueIsNull)
+ 			pfree(DatumGetPointer(peraggstate->transValue));
+ 	}
+ 
+ 	MemoryContextSwitchTo(oldContext);
+ 	peraggstate->transValue = newVal;
+ 	peraggstate->transValueIsNull = fcinfo->isnull;
+ 
+ 	return true;
+ }
+ 
+ 
+ /*
   * finalize_windowaggregate
   * parallel to finalize_aggregate in nodeAgg.c
   */
*************** finalize_windowaggregate(WindowAggState 
*** 370,376 ****
--- 599,607 ----
  		}
  		else
  		{
+ 			winstate->calledaggno = perfuncstate->aggno;
  			*result = FunctionCallInvoke(&fcinfo);
+ 			winstate->calledaggno = -1;
  			*isnull = fcinfo.isnull;
  		}
  	}
*************** finalize_windowaggregate(WindowAggState 
*** 392,402 ****
  	MemoryContextSwitchTo(oldContext);
  }
  
  /*
   * eval_windowaggregates
   * evaluate plain aggregates being used as window functions
   *
!  * Much of this is duplicated from nodeAgg.c.  But NOTE that we expect to be
   * able to call aggregate final functions repeatedly after aggregating more
   * data onto the same transition value.  This is not a behavior required by
   * nodeAgg.c.
--- 623,636 ----
  	MemoryContextSwitchTo(oldContext);
  }
  
+ 
  /*
   * eval_windowaggregates
   * evaluate plain aggregates being used as window functions
   *
!  * This differes from nodeAgg.c in two ways. First, if the window's frame
!  * start position moves, we use the inverse transfer function (if it exists)
!  * to remove values from the transition value. And second, we expect to be
   * able to call aggregate final functions repeatedly after aggregating more
   * data onto the same transition value.  This is not a behavior required by
   * nodeAgg.c.
*************** eval_windowaggregates(WindowAggState *wi
*** 406,417 ****
  {
  	WindowStatePerAgg peraggstate;
  	int			wfuncno,
! 				numaggs;
! 	int			i;
  	MemoryContext oldContext;
  	ExprContext *econtext;
  	WindowObject agg_winobj;
! 	TupleTableSlot *agg_row_slot;
  
  	numaggs = winstate->numaggs;
  	if (numaggs == 0)
--- 640,654 ----
  {
  	WindowStatePerAgg peraggstate;
  	int			wfuncno,
! 				numaggs,
! 				numaggs_restart = 0,
! 				i;
! 	int64		aggregatedupto_nonrestarted;
  	MemoryContext oldContext;
  	ExprContext *econtext;
  	WindowObject agg_winobj;
! 	TupleTableSlot *agg_row_slot = winstate->agg_row_slot;
! 	TupleTableSlot *temp_slot = winstate->temp_slot_1;
  
  	numaggs = winstate->numaggs;
  	if (numaggs == 0)
*************** eval_windowaggregates(WindowAggState *wi
*** 420,426 ****
  	/* 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;
  
  	/*
  	 * Currently, we support only a subset of the SQL-standard window framing
--- 657,662 ----
*************** eval_windowaggregates(WindowAggState *wi
*** 438,446 ****
  	 * damage the running transition value, but we have the same assumption in
  	 * nodeAgg.c too (when it rescans an existing hash table).
  	 *
! 	 * For other frame start rules, we discard the aggregate state and re-run
! 	 * the aggregates whenever the frame head row moves.  We can still
! 	 * optimize as above whenever successive rows share the same frame head.
  	 *
  	 * In many common cases, multiple rows share the same frame and hence the
  	 * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
--- 674,693 ----
  	 * damage the running transition value, but we have the same assumption in
  	 * nodeAgg.c too (when it rescans an existing hash table).
  	 *
! 	 * We can still optimize as above whenever successive rows share the same
! 	 * frame head, but if the frame head moves beyond the aggregated base point
! 	 * we use the aggregate function's inverse transition function. This
! 	 * removes the tuple from aggregation and restores the aggregate's current
! 	 * state to what it would be if the removed row had never been aggregated
! 	 * in the first place. Inverse transition functions may optionally return
! 	 * NULL, this indicates that the function was unable to remove the tuple
! 	 * from aggregation, when this happens we must perform the aggregation all
! 	 * over again for all tuples in the new frame boundary.
! 	 *
! 	 * If the aggregate function does not have a inverse transition function
! 	 * and the frame head moves beyond the aggregated position then we must
! 	 * discard the aggregated state and re-aggregate similar to how we would
! 	 * have to if the inverse transition function had returned NULL.
  	 *
  	 * In many common cases, multiple rows share the same frame and hence the
  	 * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
*************** eval_windowaggregates(WindowAggState *wi
*** 452,526 ****
  	 * 'aggregatedupto' keeps track of the first row that has not yet been
  	 * accumulated into the aggregate transition values.  Whenever we start a
  	 * new peer group, we accumulate forward to the end of the peer group.
- 	 *
- 	 * TODO: Rerunning aggregates from the frame start can be pretty slow. For
- 	 * some aggregates like SUM and COUNT we could avoid that by implementing
- 	 * a "negative transition function" that would be called for each row as
- 	 * it exits the frame.	We'd have to think about avoiding recalculation of
- 	 * volatile arguments of aggregate functions, too.
  	 */
  
  	/*
  	 * First, update the frame head position.
  	 */
! 	update_frameheadpos(agg_winobj, winstate->temp_slot_1);
  
  	/*
! 	 * Initialize aggregates on first call for partition, or if the frame head
! 	 * position moved since last time.
  	 */
! 	if (winstate->currentpos == 0 ||
! 		winstate->frameheadpos != winstate->aggregatedbase)
  	{
- 		/*
- 		 * Discard transient aggregate values
- 		 */
- 		MemoryContextResetAndDeleteChildren(winstate->aggcontext);
- 
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
  		}
  
  		/*
! 		 * If we created a mark pointer for aggregates, keep it pushed up to
! 		 * frame head, so that tuplestore can discard unnecessary rows.
  		 */
! 		if (agg_winobj->markptr >= 0)
! 			WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
  
  		/*
! 		 * Initialize for loop below
  		 */
! 		ExecClearTuple(agg_row_slot);
! 		winstate->aggregatedbase = winstate->frameheadpos;
! 		winstate->aggregatedupto = winstate->frameheadpos;
  	}
  
  	/*
! 	 * In UNBOUNDED_FOLLOWING mode, we don't have to recalculate aggregates
! 	 * except when the frame head moves.  In END_CURRENT_ROW mode, we only
! 	 * have to recalculate when the frame head moves or currentpos has
! 	 * advanced past the place we'd aggregated up to.  Check for these cases
! 	 * and if so, reuse the saved result values.
  	 */
! 	if ((winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
! 								   FRAMEOPTION_END_CURRENT_ROW)) &&
! 		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;
  	}
  
  	/*
--- 699,885 ----
  	 * 'aggregatedupto' keeps track of the first row that has not yet been
  	 * accumulated into the aggregate transition values.  Whenever we start a
  	 * new peer group, we accumulate forward to the end of the peer group.
  	 */
  
  	/*
  	 * First, update the frame head position.
+ 	 *
+ 	 * The frame head should never move backwards, and the code below wouldn't
+ 	 * cope if it did, so for safety we complain if it does.
  	 */
! 	update_frameheadpos(agg_winobj, temp_slot);
! 	if (winstate->frameheadpos < winstate->aggregatedbase)
! 		elog(ERROR, "frame moved backwards unexpectedly");
  
  	/*
! 	 * If the frame didn't change compared to the previous row, we can re-use
! 	 * the cached result. Since we don't know the current frame's end yet, we
! 	 * cannot check that the obvious way. But we can exploit that if the frame
! 	 * end is UNBOUNDED FOLLOWING or CURRENT ROW, then whenever the current
! 	 * row lies within the previous row's frame, the two frame's ends must
! 	 * coincide. Note that for the first row, aggregatedbase = aggregatedupto,
! 	 * so we don't need to check for that explicitly here.
  	 */
! 	if (winstate->aggregatedbase == winstate->frameheadpos &&
! 		(winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
! 								   FRAMEOPTION_END_CURRENT_ROW)) &&
! 		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;
+ 	}
  
+ 	/* Initialize restart flags.
+ 	 *
+ 	 * We restart the aggregation
+ 	 *   - if we're processing the first row in the partition, or
+ 	 *   - if we the frame's head moved and we cannot use an inverse
+ 	 *     transition function, 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 we're going to remove *all* rows from the aggregation anyway, a
+ 	 * restart surely is faster.
+ 	 */
+ 	for (i = 0; i < numaggs; i++)
+ 	{
+ 		peraggstate = &winstate->peragg[i];
+ 		if (winstate->currentpos == 0 ||
+ 			(winstate->aggregatedbase < winstate->frameheadpos &&
+ 			!peraggstate->use_invtransfn) ||
+ 			winstate->aggregatedupto <= winstate->frameheadpos)
+ 		{
+ 			peraggstate->restart = true;
+ 			numaggs_restart++;
+ 		}
+ 		else
+ 			peraggstate->restart = false;
+ 	}
+ 
+ 	/*
+ 	 * Attempt to update aggregatedbase to match the frame's head by
+ 	 * removing those inputs from the aggregations which fell off the top
+ 	 * of the frame. This can fail, i.e. retreat_windowaggregate() can
+ 	 * return false, in which case we restart that aggregate below.
+ 	 *
+ 	 * Aftwards, aggregatedbase equals frameheadpos.
+ 	 */
+ 	while(winstate->aggregatedbase < winstate->frameheadpos)
+ 	{
  		/*
! 		 * Fetch the tuple where the current aggregation started from.
! 		 * This should never fail as we should have been here before.
  		 */
! 		if (!window_gettupleslot(agg_winobj, winstate->aggregatedbase,
! 								 temp_slot))
! 			elog(ERROR, "Unable to find tuple in tuplestore");
! 
! 		/* Set tuple context for evaluation of aggregate arguments */
! 		winstate->tmpcontext->ecxt_outertuple = temp_slot;
  
  		/*
! 		 * Perform the inverse transition for each aggregate function in
! 		 * the window, unless it has already been marked as needing a
! 		 * restart.
  		 */
! 		for (i = 0; i < numaggs; i++)
! 		{
! 			bool	ok;
! 
! 			peraggstate = &winstate->peragg[i];
! 			if (peraggstate->restart)
! 				continue;
! 
! 			wfuncno = peraggstate->wfuncno;
! 			ok = retreat_windowaggregate(winstate,
! 										 &winstate->perfunc[wfuncno],
! 										 peraggstate);
! 			if (!ok)
! 			{
! 				/* Inverse transition function has failed, must restart */
! 				peraggstate->restart = true;
! 				numaggs_restart++;
! 			}
! 		}
! 
! 		/* Reset per-input-tuple context after each tuple */
! 		ResetExprContext(winstate->tmpcontext);
! 
! 		/* And advance the aggregated-row state */
! 		winstate->aggregatedbase++;
! 		ExecClearTuple(temp_slot);
! 
! 		/* If no more retreatable aggregates are left, we stop early */
! 		if (numaggs_restart == numaggs)
! 		{
! 			winstate->aggregatedbase = winstate->frameheadpos;
! 			break;
! 		}
  	}
  
  	/*
! 	 * If we created a mark pointer for aggregates, keep it pushed up to
! 	 * frame head, so that tuplestore can discard unnecessary rows.
  	 */
! 	if (agg_winobj->markptr >= 0)
! 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
! 
! 	/*
! 	 * Then restart the aggregates which require it.
! 	 *
! 	 * We assume that aggregates using the shared context always restart
! 	 * if *any* aggregate restarts, and we may thus cleanup the shared
! 	 * aggcontext if that is the case. The private aggcontexts are reset
! 	 * by initialize_windowaggregate() if their owning aggregate restarts,
! 	 * otherwise we just pfree() the cached result.
! 	 */
! 	if (numaggs_restart > 0)
! 		MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
! 	for (i = 0; i < numaggs; i++)
  	{
! 		peraggstate = &winstate->peragg[i];
! 		/* Aggregates using the shared ctx must restart if *any* agg does */
! 		Assert(!peraggstate->aggcontext_is_shared ||
! 			   !numaggs_restart || peraggstate->restart);
! 		if (!peraggstate->restart && !peraggstate->resultValueIsNull &&
! 			!peraggstate->resulttypeByVal)
! 		{
! 			pfree(DatumGetPointer(peraggstate->resultValue));
! 			peraggstate->resultValue = (Datum) 0;
! 			peraggstate->resultValueIsNull = true;
! 		}
! 		else if (peraggstate->restart)
  		{
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
  		}
! 	}
! 
! 	/*
! 	 * Non-restarted aggregates now contain the rows between aggregatedbase
! 	 * (i.e. frameheadpos) and aggregatedupto, and restarted aggregates
! 	 * contain no rows. If there are any restarted aggregates, we must thus
! 	 * begin aggregating anew at frameheadpos, otherwise we may simply
! 	 * continue at aggregatedupto. Since we possibly reset aggregatedupto, we
! 	 * must remember the old value to know how long to skip non-restarted
! 	 * aggregates. If we modify aggregatedupto, we must also clear
! 	 * agg_row_slot, per the loop invariant below.
! 	 */
! 	aggregatedupto_nonrestarted = winstate->aggregatedupto;
! 	if (numaggs_restart > 0 &&
! 		winstate->aggregatedupto != winstate->frameheadpos)
! 	{
! 		winstate->aggregatedupto = winstate->frameheadpos;
! 		ExecClearTuple(agg_row_slot);
  	}
  
  	/*
*************** eval_windowaggregates(WindowAggState *wi
*** 551,556 ****
--- 910,920 ----
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
+ 			/* Non-restarted aggs skip until aggregatedupto_nonrestarted */
+ 			if (winstate->aggregatedupto < aggregatedupto_nonrestarted &&
+ 				!peraggstate->restart)
+ 				continue;
+ 
  			wfuncno = peraggstate->wfuncno;
  			advance_windowaggregate(winstate,
  									&winstate->perfunc[wfuncno],
*************** eval_windowaggregates(WindowAggState *wi
*** 565,570 ****
--- 929,945 ----
  		ExecClearTuple(agg_row_slot);
  	}
  
+ 	/* The frame's end is not supposed to move backwards, ever */
+ 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
+ 
+ 	/* Update statistics */
+ 	if (numaggs_restart > 0)
+ 		winstate->aggfwdtrans += (winstate->aggregatedupto
+ 								  - winstate->frameheadpos);
+ 	else
+ 		winstate->aggfwdtrans += (winstate->aggregatedupto
+ 								  - aggregatedupto_nonrestarted);
+ 
  	/*
  	 * finalize aggregates and fill result/isnull fields.
  	 */
*************** eval_windowaggregates(WindowAggState *wi
*** 589,616 ****
  		 * advance that the next row can't possibly share the same frame. Is
  		 * it worth detecting that and skipping this code?
  		 */
! 		if (!peraggstate->resulttypeByVal)
  		{
! 			/*
! 			 * clear old resultValue in order not to leak memory.  (Note: the
! 			 * new result can't possibly be the same datum as old resultValue,
! 			 * because we never passed it to the trans function.)
! 			 */
! 			if (!peraggstate->resultValueIsNull)
! 				pfree(DatumGetPointer(peraggstate->resultValue));
! 
! 			/*
! 			 * If pass-by-ref, copy it into our aggregate context.
! 			 */
! 			if (!*isnull)
! 			{
! 				oldContext = MemoryContextSwitchTo(winstate->aggcontext);
! 				peraggstate->resultValue =
! 					datumCopy(*result,
! 							  peraggstate->resulttypeByVal,
! 							  peraggstate->resulttypeLen);
! 				MemoryContextSwitchTo(oldContext);
! 			}
  		}
  		else
  		{
--- 964,977 ----
  		 * advance that the next row can't possibly share the same frame. Is
  		 * it worth detecting that and skipping this code?
  		 */
! 		if (!peraggstate->resulttypeByVal && !*isnull)
  		{
! 			oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
! 			peraggstate->resultValue =
! 				datumCopy(*result,
! 						  peraggstate->resulttypeByVal,
! 						  peraggstate->resulttypeLen);
! 			MemoryContextSwitchTo(oldContext);
  		}
  		else
  		{
*************** eval_windowfunction(WindowAggState *wins
*** 651,656 ****
--- 1012,1018 ----
  	/* Just in case, make all the regular argument slots be null */
  	memset(fcinfo.argnull, true, perfuncstate->numArguments);
  
+ 	winstate->calledaggno = -1;
  	*result = FunctionCallInvoke(&fcinfo);
  	*isnull = fcinfo.isnull;
  
*************** spool_tuples(WindowAggState *winstate, i
*** 794,800 ****
  	 * becomes quite expensive due to frequent buffer flushes.	It's cheaper
  	 * to force the entire partition to get spooled in one go.
  	 *
! 	 * XXX this is a horrid kluge --- it'd be better to fix the performance
  	 * problem inside tuplestore.  FIXME
  	 */
  	if (!tuplestore_in_memory(winstate->buffer))
--- 1156,1162 ----
  	 * becomes quite expensive due to frequent buffer flushes.	It's cheaper
  	 * to force the entire partition to get spooled in one go.
  	 *
! 	 * XXX this is a horrid kludge --- it'd be better to fix the performance
  	 * problem inside tuplestore.  FIXME
  	 */
  	if (!tuplestore_in_memory(winstate->buffer))
*************** release_partition(WindowAggState *winsta
*** 869,875 ****
  	 * aggregates might have allocated data we don't have direct pointers to.
  	 */
  	MemoryContextResetAndDeleteChildren(winstate->partcontext);
! 	MemoryContextResetAndDeleteChildren(winstate->aggcontext);
  
  	if (winstate->buffer)
  		tuplestore_end(winstate->buffer);
--- 1231,1240 ----
  	 * aggregates might have allocated data we don't have direct pointers to.
  	 */
  	MemoryContextResetAndDeleteChildren(winstate->partcontext);
! 	MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
! 	for (i = 0; i < winstate->numaggs; ++i)
! 		if (!winstate->peragg[i].aggcontext_is_shared)
! 			MemoryContextResetAndDeleteChildren(winstate->peragg[i].aggcontext);
  
  	if (winstate->buffer)
  		tuplestore_end(winstate->buffer);
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1419,1425 ****
  	int			numfuncs,
  				wfuncno,
  				numaggs,
! 				aggno;
  	ListCell   *l;
  
  	/* check for unsupported flags */
--- 1784,1791 ----
  	int			numfuncs,
  				wfuncno,
  				numaggs,
! 				aggno,
! 				numaggs_invtrans;
  	ListCell   *l;
  
  	/* check for unsupported flags */
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1450,1457 ****
  							  ALLOCSET_DEFAULT_INITSIZE,
  							  ALLOCSET_DEFAULT_MAXSIZE);
  
! 	/* Create mid-lived context for aggregate trans values etc */
! 	winstate->aggcontext =
  		AllocSetContextCreate(CurrentMemoryContext,
  							  "WindowAgg_Aggregates",
  							  ALLOCSET_DEFAULT_MINSIZE,
--- 1816,1825 ----
  							  ALLOCSET_DEFAULT_INITSIZE,
  							  ALLOCSET_DEFAULT_MAXSIZE);
  
! 	/* Create mid-lived contexts for aggregate trans values etc
! 	 * Note that invertible aggregates use their own private context
! 	 */
! 	winstate->aggcontext_shared =
  		AllocSetContextCreate(CurrentMemoryContext,
  							  "WindowAgg_Aggregates",
  							  ALLOCSET_DEFAULT_MINSIZE,
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1535,1540 ****
--- 1903,1909 ----
  
  	wfuncno = -1;
  	aggno = -1;
+ 	numaggs_invtrans = 0;
  	foreach(l, winstate->funcs)
  	{
  		WindowFuncExprState *wfuncstate = (WindowFuncExprState *) lfirst(l);
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1603,1608 ****
--- 1972,1979 ----
  			peraggstate = &winstate->peragg[aggno];
  			initialize_peragg(winstate, wfunc, peraggstate);
  			peraggstate->wfuncno = wfuncno;
+ 			if (peraggstate->use_invtransfn)
+ 				numaggs_invtrans++;
  		}
  		else
  		{
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1618,1623 ****
--- 1989,1995 ----
  	/* Update numfuncs, numaggs to match number of unique functions found */
  	winstate->numfuncs = wfuncno + 1;
  	winstate->numaggs = aggno + 1;
+ 	winstate->numaggs_invtrans = numaggs_invtrans;
  
  	/* Set up WindowObject for aggregates, if needed */
  	if (winstate->numaggs > 0)
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1646,1651 ****
--- 2018,2029 ----
  	winstate->partition_spooled = false;
  	winstate->more_partitions = false;
  
+ 	/* initialize temporary data */
+ 	winstate->calledaggno = -1;
+ 
+ 	/* initialize statistics */
+ 	winstate->aggfwdtrans = 0;
+ 
  	return winstate;
  }
  
*************** void
*** 1657,1668 ****
  ExecEndWindowAgg(WindowAggState *node)
  {
  	PlanState  *outerPlan;
  
  	release_partition(node);
  
- 	pfree(node->perfunc);
- 	pfree(node->peragg);
- 
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  	ExecClearTuple(node->first_part_slot);
  	ExecClearTuple(node->agg_row_slot);
--- 2035,2044 ----
  ExecEndWindowAgg(WindowAggState *node)
  {
  	PlanState  *outerPlan;
+ 	int			i;
  
  	release_partition(node);
  
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  	ExecClearTuple(node->first_part_slot);
  	ExecClearTuple(node->agg_row_slot);
*************** ExecEndWindowAgg(WindowAggState *node)
*** 1677,1683 ****
  	ExecFreeExprContext(&node->ss.ps);
  
  	MemoryContextDelete(node->partcontext);
! 	MemoryContextDelete(node->aggcontext);
  
  	outerPlan = outerPlanState(node);
  	ExecEndNode(outerPlan);
--- 2053,2065 ----
  	ExecFreeExprContext(&node->ss.ps);
  
  	MemoryContextDelete(node->partcontext);
! 	MemoryContextDelete(node->aggcontext_shared);
! 	for(i = 0; i < node->numaggs; i++)
! 		if (!node->peragg[i].aggcontext_is_shared)
! 			MemoryContextDelete(node->peragg[i].aggcontext);
! 
! 	pfree(node->perfunc);
! 	pfree(node->peragg);
  
  	outerPlan = outerPlanState(node);
  	ExecEndNode(outerPlan);
*************** initialize_peragg(WindowAggState *winsta
*** 1735,1742 ****
--- 2117,2126 ----
  	Oid			aggtranstype;
  	AclResult	aclresult;
  	Oid			transfn_oid,
+ 				invtransfn_oid,
  				finalfn_oid;
  	Expr	   *transfnexpr,
+ 			   *invtransfnexpr,
  			   *finalfnexpr;
  	Datum		textInitVal;
  	int			i;
*************** initialize_peragg(WindowAggState *winsta
*** 1762,1767 ****
--- 2146,2152 ----
  	 */
  
  	peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
+ 	peraggstate->invtransfn_oid = invtransfn_oid = aggform->agginvtransfn;
  	peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
  
  	/* Check that aggregate owner has permission to call component fns */
*************** initialize_peragg(WindowAggState *winsta
*** 1783,1788 ****
--- 2168,2184 ----
  			aclcheck_error(aclresult, ACL_KIND_PROC,
  						   get_func_name(transfn_oid));
  		InvokeFunctionExecuteHook(transfn_oid);
+ 
+ 		if (OidIsValid(invtransfn_oid))
+ 		{
+ 			aclresult = pg_proc_aclcheck(invtransfn_oid, aggOwner,
+ 										 ACL_EXECUTE);
+ 			if (aclresult != ACLCHECK_OK)
+ 				aclcheck_error(aclresult, ACL_KIND_PROC,
+ 				get_func_name(invtransfn_oid));
+ 			InvokeFunctionExecuteHook(invtransfn_oid);
+ 		}
+ 
  		if (OidIsValid(finalfn_oid))
  		{
  			aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
*************** initialize_peragg(WindowAggState *winsta
*** 1810,1822 ****
--- 2206,2226 ----
  							wfunc->wintype,
  							wfunc->inputcollid,
  							transfn_oid,
+ 							invtransfn_oid,
  							finalfn_oid,
  							&transfnexpr,
+ 							&invtransfnexpr,
  							&finalfnexpr);
  
  	fmgr_info(transfn_oid, &peraggstate->transfn);
  	fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
  
+ 	if (OidIsValid(invtransfn_oid))
+ 	{
+ 		fmgr_info(invtransfn_oid, &peraggstate->invtransfn);
+ 		fmgr_info_set_expr((Node *) invtransfnexpr, &peraggstate->invtransfn);
+ 	}
+ 
  	if (OidIsValid(finalfn_oid))
  	{
  		fmgr_info(finalfn_oid, &peraggstate->finalfn);
*************** initialize_peragg(WindowAggState *winsta
*** 1860,1867 ****
--- 2264,2337 ----
  							wfunc->winfnoid)));
  	}
  
+ 	/*
+ 	 * Allowing only the forward transition function to be strict would
+ 	 * require handling more special cases in advance_windowaggregate() and
+ 	 * retreat_windowaggregate(), for no discernible benefit. So we ensure
+ 	 * that if the forward transition function is strict that the inverse
+ 	 * transition function is also strict. This should have been checked at
+ 	 * the aggregate function's definition time, but it's better to be safe...
+ 	 */
+ 	if (OidIsValid(invtransfn_oid) &&
+ 		peraggstate->transfn.fn_strict != peraggstate->invtransfn.fn_strict)
+ 	{
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 				errmsg("strictness of forward and inverse transition functions must match")));
+ 	}
+ 
  	ReleaseSysCache(aggTuple);
  
+ 	/*
+ 	 * We can use the inverse transition function only if the aggregate's
+ 	 * arguments don't contain calls to volatile functions. Otherwise,
+ 	 * the difference between restarting and not restarting the aggregation
+ 	 * would be user-visible. Note that this check also covers the case where
+ 	 * the FILTER's WHERE clause contains a volatile function. If the frame
+ 	 * head cannot move, we won't ever need the inverse transition function,
+ 	 * so we also mark as "don't use" in that case.
+ 	 */
+ 	if (OidIsValid(invtransfn_oid) &&
+ 		!contain_volatile_functions((Node *) wfunc) &&
+ 		!(winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING))
+ 	{
+ 		peraggstate->use_invtransfn = true;
+ 	}
+ 	else
+ 	{
+ 		peraggstate->use_invtransfn = false;
+ 	}
+ 
+ 	/*
+ 	 * Invertible aggregates use their own aggcontext.
+ 	 *
+ 	 * This is necessary because they might all restart at different times,
+ 	 * so we might never be able to reset the shared context otherwise. We
+ 	 * can't make it the aggregate's responsibility to clean up after
+ 	 * themselves, because strict aggregates must be restarted whenever we
+ 	 * remove their last non-NULL input, which the aggregate won't be aware
+ 	 * is happening. Also, just pfree()ing the transValue upon restarting
+ 	 * wouldn't help, since we'd miss any indirectly referenced data. We
+ 	 * could, in theory, declare that aggregates with a state type other then
+ 	 * "internal" musn't allocate anything in the aggcontext themselves, that
+ 	 * non-strict aggregates with state type internal must clean up after
+ 	 * themselves when their inverse transfer function returns NULL, and then
+ 	 * only use private aggcontexts for strict aggregates with state type
+ 	 * internal. But that'd be a rather grotty set of requirements.
+ 	 */
+ 	peraggstate->aggcontext_is_shared = !peraggstate->use_invtransfn;
+ 	if (!peraggstate->aggcontext_is_shared)
+ 	{
+ 		peraggstate->aggcontext =
+ 			AllocSetContextCreate(CurrentMemoryContext,
+ 								  "WindowAgg_AggregatePrivate",
+ 								  ALLOCSET_DEFAULT_MINSIZE,
+ 								  ALLOCSET_DEFAULT_INITSIZE,
+ 								  ALLOCSET_DEFAULT_MAXSIZE);
+ 	}
+ 	else
+ 		peraggstate->aggcontext = winstate->aggcontext_shared;
+ 
  	return peraggstate;
  }
  
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 9613e2a..ef84e4c 100644
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
*************** resolve_aggregate_transtype(Oid aggfunci
*** 1187,1197 ****
   * For an ordered-set aggregate, remember that agg_input_types describes
   * the direct arguments followed by the aggregated arguments.
   *
!  * transfn_oid and finalfn_oid identify the funcs to be called; the latter
!  * may be InvalidOid.
   *
!  * Pointers to the constructed trees are returned into *transfnexpr and
!  * *finalfnexpr.  The latter is set to NULL if there's no finalfn.
   */
  void
  build_aggregate_fnexprs(Oid *agg_input_types,
--- 1187,1199 ----
   * For an ordered-set aggregate, remember that agg_input_types describes
   * the direct arguments followed by the aggregated arguments.
   *
!  * transfn_oid, invtransfn_oid and finalfn_oid identify the funcs to be
!  * called; the latter two may be InvalidOid.
   *
!  * Pointers to the constructed trees are returned into *transfnexpr,
!  * *invtransfnexpr and *finalfnexpr. If there is invtransfn or finalfn, the
!  * respective pointers are set to NULL. Since use of the invtransfn is
!  * optional, NULL may be passed for invtransfnexpr.
   */
  void
  build_aggregate_fnexprs(Oid *agg_input_types,
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 1203,1210 ****
--- 1205,1214 ----
  						Oid agg_result_type,
  						Oid agg_input_collation,
  						Oid transfn_oid,
+ 						Oid invtransfn_oid,
  						Oid finalfn_oid,
  						Expr **transfnexpr,
+ 						Expr **invtransfnexpr,
  						Expr **finalfnexpr)
  {
  	Param	   *argp;
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 1249,1254 ****
--- 1253,1270 ----
  	fexpr->funcvariadic = agg_variadic;
  	*transfnexpr = (Expr *) fexpr;
  
+ 	if (OidIsValid(invtransfn_oid) && invtransfnexpr != NULL)
+ 	{
+ 		*invtransfnexpr = (Expr *) makeFuncExpr(invtransfn_oid,
+ 												agg_result_type,
+ 												args,
+ 												InvalidOid,
+ 												agg_input_collation,
+ 												COERCE_EXPLICIT_CALL);
+ 	}
+ 	else if (invtransfnexpr != NULL)
+ 		*invtransfnexpr	= NULL;
+ 
  	/* see if we have a final function */
  	if (!OidIsValid(finalfn_oid))
  	{
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 2ce8e6d..21a7347 100644
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11512,11517 ****
--- 11512,11518 ----
  	char	   *aggsig_tag;
  	PGresult   *res;
  	int			i_aggtransfn;
+ 	int			i_agginvtransfn;
  	int			i_aggfinalfn;
  	int			i_aggsortop;
  	int			i_hypothetical;
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11520,11525 ****
--- 11521,11527 ----
  	int			i_agginitval;
  	int			i_convertok;
  	const char *aggtransfn;
+ 	const char *agginvtransfn;
  	const char *aggfinalfn;
  	const char *aggsortop;
  	bool		hypothetical;
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11544,11550 ****
  	/* Get aggregate-specific details */
  	if (fout->remoteVersion >= 90400)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "(aggkind = 'h') as hypothetical, "
--- 11546,11552 ----
  	/* Get aggregate-specific details */
  	if (fout->remoteVersion >= 90400)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "(aggkind = 'h') as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11560,11565 ****
--- 11562,11568 ----
  	else if (fout->remoteVersion >= 80400)
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "false as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11575,11580 ****
--- 11578,11584 ----
  	else if (fout->remoteVersion >= 80100)
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "false as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11588,11593 ****
--- 11592,11598 ----
  	else if (fout->remoteVersion >= 70300)
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "0 AS aggsortop, "
  						  "'f'::boolean as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11600,11606 ****
  	}
  	else if (fout->remoteVersion >= 70100)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, aggfinalfn, "
  						  "format_type(aggtranstype, NULL) AS aggtranstype, "
  						  "0 AS aggsortop, "
  						  "'f'::boolean as hypothetical, "
--- 11605,11613 ----
  	}
  	else if (fout->remoteVersion >= 70100)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, "
! 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
! 						  "aggfinalfn, "
  						  "format_type(aggtranstype, NULL) AS aggtranstype, "
  						  "0 AS aggsortop, "
  						  "'f'::boolean as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11613,11618 ****
--- 11620,11626 ----
  	else
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn1 AS aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, "
  						  "(SELECT typname FROM pg_type WHERE oid = aggtranstype1) AS aggtranstype, "
  						  "0 AS aggsortop, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11627,11632 ****
--- 11635,11641 ----
  	res = ExecuteSqlQueryForSingleRow(fout, query->data);
  
  	i_aggtransfn = PQfnumber(res, "aggtransfn");
+ 	i_agginvtransfn = PQfnumber(res, "agginvtransfn");
  	i_aggfinalfn = PQfnumber(res, "aggfinalfn");
  	i_aggsortop = PQfnumber(res, "aggsortop");
  	i_hypothetical = PQfnumber(res, "hypothetical");
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11636,11641 ****
--- 11645,11651 ----
  	i_convertok = PQfnumber(res, "convertok");
  
  	aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
+ 	agginvtransfn = PQgetvalue(res, 0, i_agginvtransfn);
  	aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
  	aggsortop = PQgetvalue(res, 0, i_aggsortop);
  	hypothetical = (PQgetvalue(res, 0, i_hypothetical)[0] == 't');
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11694,11699 ****
--- 11704,11715 ----
  						  fmtId(aggtranstype));
  	}
  
+ 	if (strcmp(agginvtransfn, "-") != 0)
+ 	{
+ 		appendPQExpBuffer(details, ",\n    INVSFUNC = %s",
+ 						  agginvtransfn);
+ 	}
+ 
  	if (strcmp(aggtransspace, "0") != 0)
  	{
  		appendPQExpBuffer(details, ",\n    SSPACE = %s",
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index f189998..3412661 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** CATALOG(pg_aggregate,2600) BKI_WITHOUT_O
*** 46,51 ****
--- 46,52 ----
  	char		aggkind;
  	int16		aggnumdirectargs;
  	regproc		aggtransfn;
+ 	regproc		agginvtransfn;
  	regproc		aggfinalfn;
  	Oid			aggsortop;
  	Oid			aggtranstype;
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 68,83 ****
   * ----------------
   */
  
! #define Natts_pg_aggregate					9
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_aggfinalfn		5
! #define Anum_pg_aggregate_aggsortop			6
! #define Anum_pg_aggregate_aggtranstype		7
! #define Anum_pg_aggregate_aggtransspace		8
! #define Anum_pg_aggregate_agginitval		9
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
--- 69,85 ----
   * ----------------
   */
  
! #define Natts_pg_aggregate					10
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_agginvtransfn		5
! #define Anum_pg_aggregate_aggfinalfn		6
! #define Anum_pg_aggregate_aggsortop			7
! #define Anum_pg_aggregate_aggtranstype		8
! #define Anum_pg_aggregate_aggtransspace		9
! #define Anum_pg_aggregate_agginitval		10
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 101,277 ****
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8		-				0	20		0	"0" ));
! DATA(insert ( 2819	n 0 float8_regr_accum	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 float8_regr_accum	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 float8_regr_accum	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 float8_regr_accum	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 float8_regr_accum	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 float8_regr_accum	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 float8_regr_accum	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 float8_regr_accum	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 float8_regr_accum	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 float8_regr_accum	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 float8_regr_accum	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-					0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 int2or		-					0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 int4and		-					0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 int4or		-					0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 int8and		-					0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 int8or		-					0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 bitand		-					0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 bitor		-					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn	json_agg_finalfn	0	2281	0	_null_ ));
! DATA(insert ( 3197	n 0 json_object_agg_transfn	json_object_agg_finalfn	0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
--- 103,279 ----
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	-	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	-	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	-	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	-	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-	-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-	-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-	-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-	-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-	-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-	-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-	-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-	-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-	-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-	-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-	-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -	-				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-	-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-	-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-	-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-	-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-	-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-	-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-	-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-	-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-	-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -	-			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -	-				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-	-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-	-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	-	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	-	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
! DATA(insert ( 2819	n 0 float8_regr_accum	-	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 float8_regr_accum	-	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 float8_regr_accum	-	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 float8_regr_accum	-	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 float8_regr_accum	-	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 float8_regr_accum	-	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 float8_regr_accum	-	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 float8_regr_accum	-	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 float8_regr_accum	-	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 float8_regr_accum	-	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 int2or		-	-					0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 int4and		-	-					0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 int4or		-	-					0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 int8and		-	-					0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 int8or		-	-					0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 bitand		-	-					0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 bitor		-	-					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	-	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	-	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	-	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn		-	json_agg_finalfn		0	2281	0	_null_ ));
! DATA(insert ( 3197	n 0 json_object_agg_transfn	-	json_object_agg_finalfn	0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			-	percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			-	percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			-	percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			-	percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			-	percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			-	percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			-	mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	-	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	-	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	-	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	-	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
*************** extern Oid AggregateCreate(const char *a
*** 289,294 ****
--- 291,297 ----
  				List *parameterDefaults,
  				Oid variadicArgType,
  				List *aggtransfnName,
+ 				List *agginvtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index aed81cd..c4d4864 100644
*** a/src/include/fmgr.h
--- b/src/include/fmgr.h
*************** extern void **find_rendezvous_variable(c
*** 645,652 ****
  /*
   * Support for aggregate functions
   *
!  * These are actually in executor/nodeAgg.c, but we declare them here since
!  * the whole point is for callers to not be overly friendly with nodeAgg.
   */
  
  /* AggCheckCallContext can return one of the following codes, or 0: */
--- 645,654 ----
  /*
   * Support for aggregate functions
   *
!  * These are actually in executor/nodeAgg.c, except for AggCheckCallContext
!  * which is in execute/nodeWindowAgg.c, but we declare them here since
!  * the whole point is for callers to not be overly friendly neither nodeAgg
!  * nor nodeWindowAgg.
   */
  
  /* AggCheckCallContext can return one of the following codes, or 0: */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a301a08..ff558c6 100644
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct WindowAggState
*** 1738,1743 ****
--- 1738,1744 ----
  	List	   *funcs;			/* all WindowFunc nodes in targetlist */
  	int			numfuncs;		/* total number of window functions */
  	int			numaggs;		/* number that are plain aggregates */
+ 	int			numaggs_invtrans;	/* number that are invertible aggregates */
  
  	WindowStatePerFunc perfunc; /* per-window-function information */
  	WindowStatePerAgg peragg;	/* per-plain-aggregate information */
*************** typedef struct WindowAggState
*** 1762,1768 ****
  	Datum		endOffsetValue; /* result of endOffset evaluation */
  
  	MemoryContext partcontext;	/* context for partition-lifespan data */
! 	MemoryContext aggcontext;	/* context for each aggregate data */
  	ExprContext *tmpcontext;	/* short-term evaluation context */
  
  	bool		all_first;		/* true if the scan is starting */
--- 1763,1769 ----
  	Datum		endOffsetValue; /* result of endOffset evaluation */
  
  	MemoryContext partcontext;	/* context for partition-lifespan data */
! 	MemoryContext aggcontext_shared;	/* shared context for agg states */
  	ExprContext *tmpcontext;	/* short-term evaluation context */
  
  	bool		all_first;		/* true if the scan is starting */
*************** typedef struct WindowAggState
*** 1780,1789 ****
--- 1781,1796 ----
  	TupleTableSlot *first_part_slot;	/* first tuple of current or next
  										 * partition */
  
+ 	/* temporary data */
+ 	int			calledaggno;	/* called agg, used by AggCheckCallContext */
+ 
  	/* temporary slots for tuples fetched back from tuplestore */
  	TupleTableSlot *agg_row_slot;
  	TupleTableSlot *temp_slot_1;
  	TupleTableSlot *temp_slot_2;
+ 
+ 	/* Statistics */
+ 	double	aggfwdtrans;	/* number of forward transitions */
  } WindowAggState;
  
  /* ----------------
diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h
index 8faf991..938d408 100644
*** a/src/include/parser/parse_agg.h
--- b/src/include/parser/parse_agg.h
*************** extern void build_aggregate_fnexprs(Oid 
*** 39,46 ****
--- 39,48 ----
  						Oid agg_result_type,
  						Oid agg_input_collation,
  						Oid transfn_oid,
+ 						Oid invtransfn_oid,
  						Oid finalfn_oid,
  						Expr **transfnexpr,
+ 						Expr **invtransfnexpr,
  						Expr **finalfnexpr);
  
  #endif   /* PARSE_AGG_H */
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 58df854..080cbd2 100644
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
*************** select least_agg(variadic array[q1,q2]) 
*** 1580,1582 ****
--- 1580,1606 ----
   -4567890123456789
  (1 row)
  
+ -- built in aggregate function transition function args must match the inverse
+ -- transition function arguments.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+     AND agg.aggfnoid <= 9999;
+  proname | proname 
+ ---------+---------
+ (0 rows)
+ 
+ -- built in aggregate function transition function may only be strict if
+ -- the inverse is also strict.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict <> pinvtrns.proisstrict
+     AND agg.aggfnoid <= 9999;
+  proname | proname 
+ ---------+---------
+ (0 rows)
+ 
diff --git a/src/test/regress/expected/create_aggregate.out b/src/test/regress/expected/create_aggregate.out
index ca908d9..1a52423 100644
*** a/src/test/regress/expected/create_aggregate.out
--- b/src/test/regress/expected/create_aggregate.out
*************** alter aggregate my_rank(VARIADIC "any" O
*** 90,92 ****
--- 90,129 ----
   public | test_rank            | bigint           | VARIADIC "any" ORDER BY VARIADIC "any" | 
  (2 rows)
  
+ -- inverse transition functions
+ CREATE AGGREGATE sumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi
+ );
+ CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+ $$ SELECT $1 - $2; $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE invalidsumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_n
+ );
+ ERROR:  strictness of forward and inverse transition functions must match
+ CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+ $$ SELECT CAST($1 - $2 AS float8); $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE wrongtype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = intminus
+ );
+ ERROR:  function intminus(double precision, double precision) does not exist
+ CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+ $$ SELECT CAST($1 - $2 AS INT); $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE wrongreturntype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_int
+ );
+ ERROR:  return type of inverse transition function float8mi_int is not double precision
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 0f21fcb..8af3d23 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** SELECT nth_value_def(ten) OVER (PARTITIO
*** 1071,1073 ****
--- 1071,1307 ----
               1 |   3 |    3
  (10 rows)
  
+ --
+ --
+ -- Test the basic inverse transition function machinery
+ --
+ --
+ -- create aggregates which log their calls
+ -- invsfunc is *not* a real inverse here!
+ CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict
+ );
+ CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict,
+ 	initcond = 'I'
+ );
+ CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '+' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE AGGREGATE logging_agg_strict (text)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict
+ );
+ CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict,
+ 	initcond = 'I'
+ );
+ -- test strict and non-strict cases
+ SELECT
+ 	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+ 	logging_agg_nonstrict(v) over wnd as nstrict,
+ 	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+ 	logging_agg_strict(v::text) over wnd as strict,
+ 	logging_agg_strict_initcond(v) over wnd as strict_init
+ FROM (VALUES
+ 	(1, 1, NULL),
+ 	(1, 2, 'a'),
+ 	(1, 3, 'b'),
+ 	(1, 4, NULL),
+ 	(1, 5, NULL),
+ 	(1, 6, 'c'),
+ 	(2, 1, NULL),
+ 	(2, 2, 'x'),
+ 	(3, 1, 'z')
+ ) AS t(p, i, v)
+ WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+    row    |                    nstrict                    |                  nstrict_init                  |  strict   |  strict_init  
+ ----------+-----------------------------------------------+------------------------------------------------+-----------+---------------
+  1,1:NULL | +NULL                                         | I+NULL                                         |           | I
+  1,2:a    | +NULL+'a'                                     | I+NULL+'a'                                     | a         | I+'a'
+  1,3:b    | +NULL+'a'-NULL+'b'                            | I+NULL+'a'-NULL+'b'                            | a+'b'     | I+'a'+'b'
+  1,4:NULL | +NULL+'a'-NULL+'b'-'a'+NULL                   | I+NULL+'a'-NULL+'b'-'a'+NULL                   | a+'b'-'a' | I+'a'+'b'-'a'
+  1,5:NULL | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          |           | I
+  1,6:c    | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | c         | I+'c'
+  2,1:NULL | +NULL                                         | I+NULL                                         |           | I
+  2,2:x    | +NULL+'x'                                     | I+NULL+'x'                                     | x         | I+'x'
+  3,1:z    | +'z'                                          | I+'z'                                          | z         | I+'z'
+ (9 rows)
+ 
+ -- and again, but with filter
+ SELECT
+ 	p::text || ',' || i::text || ':' ||
+ 		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+ 	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+ 	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+ 	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+ 	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+ FROM (VALUES
+ 	(1, 1, true,  NULL),
+ 	(1, 2, false, 'a'),
+ 	(1, 3, true,  'b'),
+ 	(1, 4, false, NULL),
+ 	(1, 5, false, NULL),
+ 	(1, 6, false, 'c'),
+ 	(2, 1, false, NULL),
+ 	(2, 2, true,  'x'),
+ 	(3, 1, true,  'z')
+ ) AS t(p, i, f, v)
+ WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+    row    | nstrict_filt | nstrict_init_filt | strict_filt | strict_init_filt 
+ ----------+--------------+-------------------+-------------+------------------
+  1,1:NULL | +NULL        | I+NULL            |             | I
+  1,2:-    | +NULL        | I+NULL            |             | I
+  1,3:b    | +'b'         | I+'b'             | b           | I+'b'
+  1,4:-    | +'b'         | I+'b'             | b           | I+'b'
+  1,5:-    |              | I                 |             | I
+  1,6:-    |              | I                 |             | I
+  2,1:-    |              | I                 |             | I
+  2,2:x    | +'x'         | I+'x'             | x           | I+'x'
+  3,1:z    | +'z'         | I+'z'             | z           | I+'z'
+ (9 rows)
+ 
+ -- tests that volatile arguments disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+  row |    inverse    | noinverse 
+ -----+---------------+-----------
+  1:a | a             | a
+  2:b | a+'b'         | a+'b'
+  3:c | a+'b'-'a'+'c' | b+'c'
+ (3 rows)
+ 
+ -- tests that volatile filters disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text) filter(where true)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text) filter(where random() >= 0)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+  row |    inverse    | noinverse 
+ -----+---------------+-----------
+  1:a | a             | a
+  2:b | a+'b'         | a+'b'
+  3:c | a+'b'-'a'+'c' | b+'c'
+ (3 rows)
+ 
+ -- test that non-overlapping windows don't use inverse transitions
+ SELECT
+ 	logging_agg_strict(v::text) OVER wnd
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+ ORDER BY i;
+  logging_agg_strict 
+ --------------------
+  a
+  b
+  c
+ (3 rows)
+ 
+ -- test that returning NULL from the inverse transition functions
+ -- restarts the aggregation from scratch. The second aggregate is supposed
+ -- to test cases where only some aggregates restart, the third one checks
+ -- the one aggregate restarting doesn't cause others to restart
+ CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+ $$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE AGGREGATE sum_int_randomrestart (int4)
+ (
+ 	stype = int4,
+ 	sfunc = int4pl,
+ 	invsfunc = sum_int_randrestart_invsfunc
+ );
+ WITH
+ vs AS (
+ 	SELECT i, (random() * 100)::int4 AS v
+ 	FROM generate_series(1, 100) AS i
+ ),
+ sum_following AS (
+ 	SELECT i, SUM(v) OVER
+ 		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+ 	FROM vs
+ )
+ SELECT DISTINCT
+ 	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+ 	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+ 	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+ FROM vs
+ JOIN sum_following ON sum_following.i = vs.i
+ WINDOW fwd AS (
+ 	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ );
+  eq1 | eq2 | eq3 
+ -----+-----+-----
+  t   | t   | t
+ (1 row)
+ 
+ -- cleanup aggregates
+ DROP AGGREGATE sum_int_randomrestart (int4);
+ DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+ DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_strict(text);
+ DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+ DROP FUNCTION logging_sfunc_strict(text, anyelement);
+ DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_nonstrict(anyelement);
+ DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+ DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+ --
+ --
+ -- Test the arithmetic inverse transition functions
+ --
+ --
+ --
+ --
+ -- Test the MIN, MAX and boolean inverse transition functions
+ --
+ --
+ --
+ --
+ -- Tests of the collecting inverse transition functions
+ --
+ --
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 8096a6f..34f7004 100644
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
*************** drop view aggordview1;
*** 590,592 ****
--- 590,609 ----
  -- variadic aggregates
  select least_agg(q1,q2) from int8_tbl;
  select least_agg(variadic array[q1,q2]) from int8_tbl;
+ -- built in aggregate function transition function args must match the inverse
+ -- transition function arguments.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+     AND agg.aggfnoid <= 9999;
+ 
+ -- built in aggregate function transition function may only be strict if
+ -- the inverse is also strict.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict <> pinvtrns.proisstrict
+     AND agg.aggfnoid <= 9999;
diff --git a/src/test/regress/sql/create_aggregate.sql b/src/test/regress/sql/create_aggregate.sql
index c76882a..130489c 100644
*** a/src/test/regress/sql/create_aggregate.sql
--- b/src/test/regress/sql/create_aggregate.sql
*************** alter aggregate my_rank(VARIADIC "any" O
*** 101,103 ****
--- 101,144 ----
    rename to test_rank;
  
  \da test_*
+ 
+ -- inverse transition functions
+ CREATE AGGREGATE sumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi
+ );
+ 
+ CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+ $$ SELECT $1 - $2; $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE invalidsumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_n
+ );
+ 
+ CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+ $$ SELECT CAST($1 - $2 AS float8); $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE wrongtype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = intminus
+ );
+ 
+ CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+ $$ SELECT CAST($1 - $2 AS INT); $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE wrongreturntype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_int
+ );
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 7297e62..117bd6c 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** SELECT nth_value_def(n := 2, val := ten)
*** 284,286 ****
--- 284,489 ----
  
  SELECT nth_value_def(ten) OVER (PARTITION BY four), ten, four
    FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten) s;
+ 
+ --
+ --
+ -- Test the basic inverse transition function machinery
+ --
+ --
+ 
+ -- create aggregates which log their calls
+ -- invsfunc is *not* a real inverse here!
+ CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ 
+ CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ 
+ CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict
+ );
+ 
+ CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict,
+ 	initcond = 'I'
+ );
+ 
+ CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '+' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE AGGREGATE logging_agg_strict (text)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict
+ );
+ 
+ CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict,
+ 	initcond = 'I'
+ );
+ 
+ -- test strict and non-strict cases
+ SELECT
+ 	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+ 	logging_agg_nonstrict(v) over wnd as nstrict,
+ 	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+ 	logging_agg_strict(v::text) over wnd as strict,
+ 	logging_agg_strict_initcond(v) over wnd as strict_init
+ FROM (VALUES
+ 	(1, 1, NULL),
+ 	(1, 2, 'a'),
+ 	(1, 3, 'b'),
+ 	(1, 4, NULL),
+ 	(1, 5, NULL),
+ 	(1, 6, 'c'),
+ 	(2, 1, NULL),
+ 	(2, 2, 'x'),
+ 	(3, 1, 'z')
+ ) AS t(p, i, v)
+ WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+ 
+ -- and again, but with filter
+ SELECT
+ 	p::text || ',' || i::text || ':' ||
+ 		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+ 	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+ 	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+ 	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+ 	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+ FROM (VALUES
+ 	(1, 1, true,  NULL),
+ 	(1, 2, false, 'a'),
+ 	(1, 3, true,  'b'),
+ 	(1, 4, false, NULL),
+ 	(1, 5, false, NULL),
+ 	(1, 6, false, 'c'),
+ 	(2, 1, false, NULL),
+ 	(2, 2, true,  'x'),
+ 	(3, 1, true,  'z')
+ ) AS t(p, i, f, v)
+ WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+ 
+ -- tests that volatile arguments disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- tests that volatile filters disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text) filter(where true)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text) filter(where random() >= 0)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- test that non-overlapping windows don't use inverse transitions
+ SELECT
+ 	logging_agg_strict(v::text) OVER wnd
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- test that returning NULL from the inverse transition functions
+ -- restarts the aggregation from scratch. The second aggregate is supposed
+ -- to test cases where only some aggregates restart, the third one checks
+ -- the one aggregate restarting doesn't cause others to restart
+ CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+ $$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE AGGREGATE sum_int_randomrestart (int4)
+ (
+ 	stype = int4,
+ 	sfunc = int4pl,
+ 	invsfunc = sum_int_randrestart_invsfunc
+ );
+ 
+ WITH
+ vs AS (
+ 	SELECT i, (random() * 100)::int4 AS v
+ 	FROM generate_series(1, 100) AS i
+ ),
+ sum_following AS (
+ 	SELECT i, SUM(v) OVER
+ 		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+ 	FROM vs
+ )
+ SELECT DISTINCT
+ 	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+ 	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+ 	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+ FROM vs
+ JOIN sum_following ON sum_following.i = vs.i
+ WINDOW fwd AS (
+ 	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ );
+ 
+ -- cleanup aggregates
+ DROP AGGREGATE sum_int_randomrestart (int4);
+ DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+ DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_strict(text);
+ DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+ DROP FUNCTION logging_sfunc_strict(text, anyelement);
+ DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_nonstrict(anyelement);
+ DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+ DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+ 
+ --
+ --
+ -- Test the arithmetic inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Test the MIN, MAX and boolean inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Tests of the collecting inverse transition functions
+ --
+ --
