From eeb03c4bddf07385372dda44558874a9f25dbb1b Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Wed, 20 Sep 2023 15:41:00 +0530
Subject: [PATCH v27 4/8] Support infinite interval values in sum and avg
 window functions

Results of arithmetic operations involing both infinity and -infinity
intervals are undefined. Sliding window functions
accumulate and discard values as the window slides through the data.
Even if the data may have both infinities, none of windows may have both
of them together and thus produce definite results. Hence we can not use
normal operator functions to accumulate and discard values. Instead we
maintain a count of both infinities seen respectively in an aggregate
state and compute the aggregate for each window based on the accumulated
finite value and the counts of infinities, all of which are updated as
the window slides through the data.

Initial patch by Jian He, significantly edited by Ashutosh Bapat
---
 src/backend/utils/adt/timestamp.c    | 407 ++++++++++++++++++---------
 src/include/catalog/pg_aggregate.dat |  23 +-
 src/include/catalog/pg_proc.dat      |  20 +-
 src/test/regress/expected/window.out |  62 ++++
 src/test/regress/sql/window.sql      |  43 +++
 src/tools/pgindent/typedefs.list     |   1 +
 6 files changed, 400 insertions(+), 156 deletions(-)

diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index a70639db58..450b547036 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -73,6 +73,21 @@ typedef struct
 	pg_tz	   *attimezone;
 } generate_series_timestamptz_fctx;
 
+/*
+ * The transition datatype for interval aggregates is declared as Internal. It's
+ * a pointer to a IntervalAggState allocated in the aggregate context.
+ */
+typedef struct IntervalAggState
+{
+	int64		N;				/* count of processed intervals */
+	Interval	sumX;			/* sum of processed intervals */
+	/* These counts are *not* included in N!  Use IA_TOTAL_COUNT() as needed */
+	int64		pInfcount;		/* count of +Inf values */
+	int64		nInfcount;		/* count of -Inf values */
+} IntervalAggState;
+
+#define IA_TOTAL_COUNT(ia) \
+	((ia)->N + (ia)->pInfcount + (ia)->nInfcount)
 
 static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
 static Timestamp dt2local(Timestamp dt, int timezone);
@@ -82,6 +97,8 @@ static TimestampTz timestamp2timestamptz(Timestamp timestamp);
 static Timestamp timestamptz2timestamp(TimestampTz timestamp);
 
 static void interval_um_internal(Interval *interval, Interval *result);
+static void finite_interval_pl(Interval *result, Interval *span1,
+							   Interval *span2);
 
 /* common code for timestamptypmodin and timestamptztypmodin */
 static int32
@@ -3443,34 +3460,7 @@ interval_pl(PG_FUNCTION_ARGS)
 	else if (INTERVAL_NOT_FINITE(span2))
 		memcpy(result, span2, sizeof(Interval));
 	else
-	{
-		result->month = span1->month + span2->month;
-		/* overflow check copied from int4pl */
-		if (SAMESIGN(span1->month, span2->month) &&
-			!SAMESIGN(result->month, span1->month))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("interval out of range")));
-
-		result->day = span1->day + span2->day;
-		if (SAMESIGN(span1->day, span2->day) &&
-			!SAMESIGN(result->day, span1->day))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("interval out of range")));
-
-		result->time = span1->time + span2->time;
-		if (SAMESIGN(span1->time, span2->time) &&
-			!SAMESIGN(result->time, span1->time))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("interval out of range")));
-
-		if (INTERVAL_NOT_FINITE(result))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("interval out of range")));
-	}
+		finite_interval_pl(result, span1, span2);
 
 	PG_RETURN_INTERVAL_P(result);
 }
@@ -3856,161 +3846,300 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
 
 
 /*
- * interval_accum, interval_accum_inv, and interval_avg implement the
- * AVG(interval) aggregate.
+ * Prepare state data for an interval aggregate function that needs to compute
+ * sum and count.
+ */
+static IntervalAggState *
+makeIntervalAggState(FunctionCallInfo fcinfo)
+{
+	IntervalAggState *state;
+	MemoryContext agg_context;
+	MemoryContext old_context;
+
+	if (!AggCheckCallContext(fcinfo, &agg_context))
+		elog(ERROR, "aggregate function called in non-aggregate context");
+
+	old_context = MemoryContextSwitchTo(agg_context);
+
+	state = (IntervalAggState *) palloc0(sizeof(IntervalAggState));
+
+	MemoryContextSwitchTo(old_context);
+
+	return state;
+}
+
+/*
+ * Function to add two finite intervals.
  *
- * The transition datatype for this aggregate is a 2-element array of
- * intervals, where the first is the running sum and the second contains
- * the number of values so far in its 'time' field.  This is a bit ugly
- * but it beats inventing a specialized datatype for the purpose.
+ * We handle non-finite intervals in different ways when accumulating intervals
+ * and adding two intervals respectively. But the addition of finite interval
+ * has same implementation in both these cases.
  */
+static void
+finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
+{
+	Assert(!INTERVAL_NOT_FINITE(span1));
+	Assert(!INTERVAL_NOT_FINITE(span2));
 
-Datum
-interval_accum(PG_FUNCTION_ARGS)
+	result->month = span1->month + span2->month;
+	/* overflow check copied from int4pl */
+	if (SAMESIGN(span1->month, span2->month) &&
+		!SAMESIGN(result->month, span1->month))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+				 errmsg("interval out of range")));
+
+	result->day = span1->day + span2->day;
+	if (SAMESIGN(span1->day, span2->day) &&
+		!SAMESIGN(result->day, span1->day))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+				 errmsg("interval out of range")));
+
+	result->time = span1->time + span2->time;
+	if (SAMESIGN(span1->time, span2->time) &&
+		!SAMESIGN(result->time, span1->time))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+				 errmsg("interval out of range")));
+
+	if (INTERVAL_NOT_FINITE(result))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+				 errmsg("interval out of range")));
+}
+
+/*
+ * Accumulate a new input value for interval aggregate functions.
+ */
+static void
+do_interval_accum(IntervalAggState *state, Interval *newval)
 {
-	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
-	Interval   *newval = PG_GETARG_INTERVAL_P(1);
-	Datum	   *transdatums;
-	int			ndatums;
-	Interval	sumX,
-				N;
-	Interval   *newsum;
-	ArrayType  *result;
 
-	deconstruct_array(transarray,
-					  INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
-					  &transdatums, NULL, &ndatums);
-	if (ndatums != 2)
-		elog(ERROR, "expected 2-element interval array");
+	Interval	temp;
+
+	/* Count -infinity inputs separately from all else */
+	if (INTERVAL_IS_NOBEGIN(newval))
+	{
+		state->nInfcount++;
+		return;
+	}
+
+	/* Count infinity inputs separately from all else */
+	if (INTERVAL_IS_NOEND(newval))
+	{
+		state->pInfcount++;
+		return;
+	}
+
+	memcpy(&temp, &state->sumX, sizeof(Interval));
+	finite_interval_pl(&state->sumX, &temp, newval);
+	state->N++;
+}
 
-	sumX = *(DatumGetIntervalP(transdatums[0]));
-	N = *(DatumGetIntervalP(transdatums[1]));
+/*
+ * Transition function for interval aggregates.
+ */
+Datum
+interval_avg_accum(PG_FUNCTION_ARGS)
+{
+	IntervalAggState *state;
 
-	newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
-												   IntervalPGetDatum(&sumX),
-												   IntervalPGetDatum(newval)));
-	N.time += 1;
+	state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
 
-	transdatums[0] = IntervalPGetDatum(newsum);
-	transdatums[1] = IntervalPGetDatum(&N);
+	/* Create the state data on the first call */
+	if (state == NULL)
+		state = makeIntervalAggState(fcinfo);
 
-	result = construct_array(transdatums, 2,
-							 INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+	if (!PG_ARGISNULL(1))
+		do_interval_accum(state, PG_GETARG_INTERVAL_P(1));
 
-	PG_RETURN_ARRAYTYPE_P(result);
+	PG_RETURN_POINTER(state);
 }
 
+/*
+ * Combine function for interval aggregates.
+ */
 Datum
-interval_combine(PG_FUNCTION_ARGS)
+interval_avg_combine(PG_FUNCTION_ARGS)
 {
-	ArrayType  *transarray1 = PG_GETARG_ARRAYTYPE_P(0);
-	ArrayType  *transarray2 = PG_GETARG_ARRAYTYPE_P(1);
-	Datum	   *transdatums1;
-	Datum	   *transdatums2;
-	int			ndatums1;
-	int			ndatums2;
-	Interval	sum1,
-				N1;
-	Interval	sum2,
-				N2;
+	IntervalAggState *state1;
+	IntervalAggState *state2;
+
+	state1 = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+	state2 = PG_ARGISNULL(1) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(1);
 
-	Interval   *newsum;
-	ArrayType  *result;
+	if (state2 == NULL)
+		PG_RETURN_POINTER(state1);
+
+	/* manually copy all fields from state2 to state1 */
+	if (state1 == NULL)
+	{
+		state1 = makeIntervalAggState(fcinfo);
 
-	deconstruct_array(transarray1,
-					  INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
-					  &transdatums1, NULL, &ndatums1);
-	if (ndatums1 != 2)
-		elog(ERROR, "expected 2-element interval array");
+		state1->N = state2->N;
+		state1->pInfcount = state2->pInfcount;
+		state1->nInfcount = state2->nInfcount;
 
-	sum1 = *(DatumGetIntervalP(transdatums1[0]));
-	N1 = *(DatumGetIntervalP(transdatums1[1]));
+		state1->sumX.day = state2->sumX.day;
+		state1->sumX.month = state2->sumX.month;
+		state1->sumX.time = state2->sumX.time;
 
-	deconstruct_array(transarray2,
-					  INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
-					  &transdatums2, NULL, &ndatums2);
-	if (ndatums2 != 2)
-		elog(ERROR, "expected 2-element interval array");
+		PG_RETURN_POINTER(state1);
+	}
+
+	state1->N += state2->N;
+	state1->pInfcount += state2->pInfcount;
+	state1->nInfcount += state2->nInfcount;
+
+	if (state2->N > 0)
+	{
+		Interval	temp;
+
+		/* Accumulate interval values */
+		memcpy(&temp, &state1->sumX, sizeof(Interval));
+		finite_interval_pl(&state1->sumX, &temp, &state2->sumX);
+	}
+
+	PG_RETURN_POINTER(state1);
+}
 
-	sum2 = *(DatumGetIntervalP(transdatums2[0]));
-	N2 = *(DatumGetIntervalP(transdatums2[1]));
+/*
+ * Remove the given interval value from the aggregated state.
+ */
+static void
+do_interval_discard(IntervalAggState *state, Interval *newval)
+{
+	/* Count -infinity inputs separately from all else */
+	if (INTERVAL_IS_NOBEGIN(newval))
+	{
+		state->nInfcount--;
+		return;
+	}
 
-	newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
-												   IntervalPGetDatum(&sum1),
-												   IntervalPGetDatum(&sum2)));
-	N1.time += N2.time;
+	/* Count infinity inputs separately from all else */
+	if (INTERVAL_IS_NOEND(newval))
+	{
+		state->pInfcount--;
+		return;
+	}
 
-	transdatums1[0] = IntervalPGetDatum(newsum);
-	transdatums1[1] = IntervalPGetDatum(&N1);
+	/* Handle to be discarded finite value. */
+	if (state->N > 0)
+	{
+		Interval	temp;
+		Interval	neg_val;
 
-	result = construct_array(transdatums1, 2,
-							 INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+		neg_val.day = -newval->day;
+		neg_val.month = -newval->month;
+		neg_val.time = -newval->time;
 
-	PG_RETURN_ARRAYTYPE_P(result);
+		memcpy(&temp, &state->sumX, sizeof(Interval));
+		finite_interval_pl(&state->sumX, &temp, &neg_val);
+	}
+	else
+	{
+		/* All values discarded, reset the state */
+		memset(&state->sumX, 0, sizeof(state->sumX));
+	}
+	state->N--;
 }
 
+/*
+ * Generic inverse transition function for interval aggregates
+ */
 Datum
 interval_accum_inv(PG_FUNCTION_ARGS)
 {
-	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
-	Interval   *newval = PG_GETARG_INTERVAL_P(1);
-	Datum	   *transdatums;
-	int			ndatums;
-	Interval	sumX,
-				N;
-	Interval   *newsum;
-	ArrayType  *result;
+	IntervalAggState *state;
 
-	deconstruct_array(transarray,
-					  INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
-					  &transdatums, NULL, &ndatums);
-	if (ndatums != 2)
-		elog(ERROR, "expected 2-element interval array");
+	state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
 
-	sumX = *(DatumGetIntervalP(transdatums[0]));
-	N = *(DatumGetIntervalP(transdatums[1]));
+	/* Should not get here with no state */
+	if (state == NULL)
+		elog(ERROR, "interval_accum_inv called with NULL state");
 
-	newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
-												   IntervalPGetDatum(&sumX),
-												   IntervalPGetDatum(newval)));
-	N.time -= 1;
+	if (!PG_ARGISNULL(1))
+		do_interval_discard(state, PG_GETARG_INTERVAL_P(1));
 
-	transdatums[0] = IntervalPGetDatum(newsum);
-	transdatums[1] = IntervalPGetDatum(&N);
+	PG_RETURN_POINTER(state);
+}
 
-	result = construct_array(transdatums, 2,
-							 INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+/* avg(interval) aggregate final function */
+Datum
+interval_avg(PG_FUNCTION_ARGS)
+{
+	IntervalAggState *state;
+	double		N_datum;
+	Interval   *sumX;
+
+	sumX = (Interval *) palloc(sizeof(Interval));
+	state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+	if (state == NULL || IA_TOTAL_COUNT(state) == 0)
+		PG_RETURN_NULL();
 
-	PG_RETURN_ARRAYTYPE_P(result);
+	/* adding plus and minus infinities gives error */
+	if (state->pInfcount > 0 && state->nInfcount > 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+				 errmsg("interval out of range.")));
+
+	if (state->pInfcount > 0)
+	{
+		INTERVAL_NOEND(sumX);
+		PG_RETURN_INTERVAL_P(sumX);
+	}
+
+	if (state->nInfcount > 0)
+	{
+		INTERVAL_NOBEGIN(sumX);
+		PG_RETURN_INTERVAL_P(sumX);
+	}
+
+	N_datum = (double) state->N;
+	sumX = &state->sumX;
+
+	PG_RETURN_DATUM(DirectFunctionCall2(interval_div, IntervalPGetDatum(sumX),
+										Float8GetDatum(N_datum)));
 }
 
+/* sum(interval) aggregate final function */
 Datum
-interval_avg(PG_FUNCTION_ARGS)
+interval_sum(PG_FUNCTION_ARGS)
 {
-	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
-	Datum	   *transdatums;
-	int			ndatums;
-	Interval	sumX,
-				N;
+	IntervalAggState *state;
+	Interval   *result;
 
-	deconstruct_array(transarray,
-					  INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
-					  &transdatums, NULL, &ndatums);
-	if (ndatums != 2)
-		elog(ERROR, "expected 2-element interval array");
+	result = (Interval *) palloc(sizeof(Interval));
 
-	sumX = *(DatumGetIntervalP(transdatums[0]));
-	N = *(DatumGetIntervalP(transdatums[1]));
+	state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
 
-	/* SQL defines AVG of no values to be NULL */
-	if (N.time == 0)
+	/* If there were no non-null inputs, return NULL */
+	if (state == NULL || IA_TOTAL_COUNT(state) == 0)
 		PG_RETURN_NULL();
 
-	return DirectFunctionCall2(interval_div,
-							   IntervalPGetDatum(&sumX),
-							   Float8GetDatum((double) N.time));
-}
+	/* adding plus and minus interval infinities is not possible */
+	if (state->pInfcount > 0 && state->nInfcount > 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+				 errmsg("interval out of range.")));
+
+	if (state->pInfcount > 0)
+	{
+		INTERVAL_NOEND(result);
+		PG_RETURN_INTERVAL_P(result);
+	}
+
+	if (state->nInfcount > 0)
+	{
+		INTERVAL_NOBEGIN(result);
+		PG_RETURN_INTERVAL_P(result);
+	}
 
+	memcpy(result, &state->sumX, sizeof(Interval));
+	PG_RETURN_INTERVAL_P(result);
+}
 
 /* timestamp_age()
  * Calculate time difference while retaining year/month fields.
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 1bc1d97d74..e2087d7be1 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -43,12 +43,13 @@
 { aggfnoid => 'avg(float8)', aggtransfn => 'float8_accum',
   aggfinalfn => 'float8_avg', aggcombinefn => 'float8_combine',
   aggtranstype => '_float8', agginitval => '{0,0,0}' },
-{ aggfnoid => 'avg(interval)', aggtransfn => 'interval_accum',
-  aggfinalfn => 'interval_avg', aggcombinefn => 'interval_combine',
-  aggmtransfn => 'interval_accum', aggminvtransfn => 'interval_accum_inv',
-  aggmfinalfn => 'interval_avg', aggtranstype => '_interval',
-  aggmtranstype => '_interval', agginitval => '{0 second,0 second}',
-  aggminitval => '{0 second,0 second}' },
+{ aggfnoid => 'avg(interval)', aggtransfn => 'interval_avg_accum',
+  aggfinalfn => 'interval_avg', aggcombinefn => 'interval_avg_combine',
+  aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+  aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
+  aggmtranstype => 'internal', aggtransspace => '128',
+  aggmtransspace => '128'
+},
 
 # sum
 { aggfnoid => 'sum(int8)', aggtransfn => 'int8_avg_accum',
@@ -72,10 +73,12 @@
 { aggfnoid => 'sum(money)', aggtransfn => 'cash_pl', aggcombinefn => 'cash_pl',
   aggmtransfn => 'cash_pl', aggminvtransfn => 'cash_mi',
   aggtranstype => 'money', aggmtranstype => 'money' },
-{ aggfnoid => 'sum(interval)', aggtransfn => 'interval_pl',
-  aggcombinefn => 'interval_pl', aggmtransfn => 'interval_pl',
-  aggminvtransfn => 'interval_mi', aggtranstype => 'interval',
-  aggmtranstype => 'interval' },
+{ aggfnoid => 'sum(interval)', aggtransfn => 'interval_avg_accum',
+  aggfinalfn => 'interval_sum', aggcombinefn => 'interval_avg_combine',
+  aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+  aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
+  aggmtranstype => 'internal', aggtransspace => '128',
+  aggmtransspace => '128'},
 { aggfnoid => 'sum(numeric)', aggtransfn => 'numeric_avg_accum',
   aggfinalfn => 'numeric_sum', aggcombinefn => 'numeric_avg_combine',
   aggserialfn => 'numeric_avg_serialize',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bc41e92677..791359a6e0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4917,17 +4917,23 @@
   prosrc => 'numeric_poly_stddev_samp' },
 
 { oid => '1843', descr => 'aggregate transition function',
-  proname => 'interval_accum', prorettype => '_interval',
-  proargtypes => '_interval interval', prosrc => 'interval_accum' },
+  proname => 'interval_avg_accum', proisstrict => 'f',
+  prorettype => 'internal', proargtypes => 'internal interval',
+  prosrc => 'interval_avg_accum' },
 { oid => '3325', descr => 'aggregate combine function',
-  proname => 'interval_combine', prorettype => '_interval',
-  proargtypes => '_interval _interval', prosrc => 'interval_combine' },
+  proname => 'interval_avg_combine', proisstrict => 'f',
+  prorettype => 'internal', proargtypes => 'internal internal',
+  prosrc => 'interval_avg_combine' },
 { oid => '3549', descr => 'aggregate transition function',
-  proname => 'interval_accum_inv', prorettype => '_interval',
-  proargtypes => '_interval interval', prosrc => 'interval_accum_inv' },
+  proname => 'interval_accum_inv', proisstrict => 'f',
+  prorettype => 'internal', proargtypes => 'internal interval',
+  prosrc => 'interval_accum_inv' },
 { oid => '1844', descr => 'aggregate final function',
   proname => 'interval_avg', prorettype => 'interval',
-  proargtypes => '_interval', prosrc => 'interval_avg' },
+  proargtypes => 'internal', prosrc => 'interval_avg' },
+{ oid => '8069', descr => 'aggregate final function',
+  proname => 'interval_sum', proisstrict => 'f', prorettype => 'interval',
+  proargtypes => 'internal', prosrc => 'interval_sum' },
 { oid => '1962', descr => 'aggregate transition function',
   proname => 'int2_avg_accum', prorettype => '_int8',
   proargtypes => '_int8 int2', prosrc => 'int2_avg_accum' },
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 69a38df10b..45262924cd 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -4375,6 +4375,68 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
  4 | 
 (4 rows)
 
+--order by.
+SELECT  x
+        ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+        ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+FROM (VALUES (NULL::interval),
+               ('infinity'::interval),
+               ('infinity'::timestamptz - now()),
+               ('6 days'::interval),
+               (NULL::interval),
+               ('-infinity'::interval)) v(x);
+     x     | curr_next_avg | prev1_curr_sum 
+-----------+---------------+----------------
+ -infinity | -infinity     | -infinity
+ @ 6 days  | infinity      | -infinity
+ infinity  | infinity      | infinity
+ infinity  | infinity      | infinity
+           |               | infinity
+           |               | 
+(6 rows)
+
+--no order by.
+SELECT  x
+        ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+        ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+        ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+        ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+        ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
+        ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
+FROM (VALUES (NULL::interval),
+               ('infinity'::interval),
+               ('infinity'::timestamptz - now()),
+               ('6 days'::interval),
+               ('7 days'::interval),
+               (NULL::interval),
+               ('-infinity'::interval)) v(x);
+     x     |   curr_next_avg   | curr_next_sum |  prev1_curr_avg   | prev1_curr_sum | prev1_next2_avg | prev1_next2_sum 
+-----------+-------------------+---------------+-------------------+----------------+-----------------+-----------------
+           | infinity          | infinity      |                   |                | infinity        | infinity
+ infinity  | infinity          | infinity      | infinity          | infinity       | infinity        | infinity
+ infinity  | infinity          | infinity      | infinity          | infinity       | infinity        | infinity
+ @ 6 days  | @ 6 days 12 hours | @ 13 days     | infinity          | infinity       | infinity        | infinity
+ @ 7 days  | @ 7 days          | @ 7 days      | @ 6 days 12 hours | @ 13 days      | -infinity       | -infinity
+           | -infinity         | -infinity     | @ 7 days          | @ 7 days       | -infinity       | -infinity
+ -infinity | -infinity         | -infinity     | -infinity         | -infinity      | -infinity       | -infinity
+(7 rows)
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+               ('3 days'::interval),
+               ('infinity'::timestamptz - now()),
+               ('6 days'::interval),
+               ('-infinity'::interval)) v(x);
+ERROR:  interval out of range.
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+               ('3 days'::interval),
+               ('infinity'::timestamptz - now()),
+               ('6 days'::interval),
+               ('-infinity'::interval)) v(x);
+ERROR:  interval out of range.
 SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
  i | sum 
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 9113a92ae0..d00423946d 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -1591,6 +1591,49 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
 SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
 
+--order by.
+SELECT  x
+        ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+        ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+FROM (VALUES (NULL::interval),
+               ('infinity'::interval),
+               ('infinity'::timestamptz - now()),
+               ('6 days'::interval),
+               (NULL::interval),
+               ('-infinity'::interval)) v(x);
+
+--no order by.
+SELECT  x
+        ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+        ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+        ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+        ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+        ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
+        ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
+FROM (VALUES (NULL::interval),
+               ('infinity'::interval),
+               ('infinity'::timestamptz - now()),
+               ('6 days'::interval),
+               ('7 days'::interval),
+               (NULL::interval),
+               ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+               ('3 days'::interval),
+               ('infinity'::timestamptz - now()),
+               ('6 days'::interval),
+               ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+               ('3 days'::interval),
+               ('infinity'::timestamptz - now()),
+               ('6 days'::interval),
+               ('-infinity'::interval)) v(x);
+
 SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 87c1aee379..236683e5ab 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1205,6 +1205,7 @@ IntegerSet
 InternalDefaultACL
 InternalGrant
 Interval
+IntervalAggState
 IntoClause
 InvalMessageArray
 InvalidationMsgsGroup
-- 
2.35.3

