diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c
index c62e3fb..5a3a31d 100644
*** a/src/backend/utils/adt/array_userfuncs.c
--- b/src/backend/utils/adt/array_userfuncs.c
*************** create_singleton_array(FunctionCallInfo 
*** 471,477 ****
  
  
  /*
!  * ARRAY_AGG aggregate function
   */
  Datum
  array_agg_transfn(PG_FUNCTION_ARGS)
--- 471,477 ----
  
  
  /*
!  * ARRAY_AGG aggregate transition function
   */
  Datum
  array_agg_transfn(PG_FUNCTION_ARGS)
*************** array_agg_transfn(PG_FUNCTION_ARGS)
*** 508,513 ****
--- 508,537 ----
  	PG_RETURN_POINTER(state);
  }
  
+ /*
+  * ARRAY_AGG aggregate inverse transition function
+  */
+ Datum
+ array_agg_invtransfn(PG_FUNCTION_ARGS)
+ {
+ 	ArrayBuildState *state;
+ 
+ 	/*
+ 	 * Shouldn't happen, but we cannot mark this function strict, since NULLs
+ 	 * need to be removed just like any other value.
+ 	 * Must also prevent direct calls because of the "interal" argument
+ 	 */
+ 	if (PG_ARGISNULL(0))
+ 		elog(ERROR, "array_agg_invtransfn called with NULL state");
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "array_agg_invtransfn called in non-aggregate context");
+ 
+ 	state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+ 	shiftArrayResult(state, 1);
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
  Datum
  array_agg_finalfn(PG_FUNCTION_ARGS)
  {
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 91df184..2dd7ecb 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** accumArrayResult(ArrayBuildState *astate
*** 4587,4592 ****
--- 4587,4593 ----
  		astate = (ArrayBuildState *) palloc(sizeof(ArrayBuildState));
  		astate->mcontext = arr_context;
  		astate->alen = 64;		/* arbitrary starting array size */
+ 		astate->offset = 0;
  		astate->dvalues = (Datum *) palloc(astate->alen * sizeof(Datum));
  		astate->dnulls = (bool *) palloc(astate->alen * sizeof(bool));
  		astate->nelems = 0;
*************** accumArrayResult(ArrayBuildState *astate
*** 4600,4606 ****
  	{
  		oldcontext = MemoryContextSwitchTo(astate->mcontext);
  		Assert(astate->element_type == element_type);
! 		/* enlarge dvalues[]/dnulls[] if needed */
  		if (astate->nelems >= astate->alen)
  		{
  			astate->alen *= 2;
--- 4601,4612 ----
  	{
  		oldcontext = MemoryContextSwitchTo(astate->mcontext);
  		Assert(astate->element_type == element_type);
! 		/*
! 		 * If the buffers are filled completely (offset must be zero then),
! 		 * we double their size. If they aren't, but the values extend to the
! 		 * end of the buffers, we reclaim wasted space at the beginning by
! 		 * moving the values to the front of the buffers.
! 		 */
  		if (astate->nelems >= astate->alen)
  		{
  			astate->alen *= 2;
*************** accumArrayResult(ArrayBuildState *astate
*** 4609,4614 ****
--- 4615,4629 ----
  			astate->dnulls = (bool *)
  				repalloc(astate->dnulls, astate->alen * sizeof(bool));
  		}
+ 		else if (astate->offset + astate->nelems >= astate->alen)
+ 		{
+ 			memmove(astate->dvalues, astate->dvalues + astate->offset,
+ 					astate->alen * sizeof(Datum));
+ 			memmove(astate->dnulls, astate->dnulls + astate->offset,
+ 					astate->alen * sizeof(bool));
+ 			astate->offset = 0;
+ 		}
+ 		Assert(astate->offset + astate->nelems < astate->alen);
  	}
  
  	/*
*************** accumArrayResult(ArrayBuildState *astate
*** 4627,4634 ****
  			dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
  	}
  
! 	astate->dvalues[astate->nelems] = dvalue;
! 	astate->dnulls[astate->nelems] = disnull;
  	astate->nelems++;
  
  	MemoryContextSwitchTo(oldcontext);
--- 4642,4649 ----
  			dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
  	}
  
! 	astate->dvalues[astate->offset + astate->nelems] = dvalue;
! 	astate->dnulls[astate->offset + astate->nelems] = disnull;
  	astate->nelems++;
  
  	MemoryContextSwitchTo(oldcontext);
*************** accumArrayResult(ArrayBuildState *astate
*** 4637,4642 ****
--- 4652,4698 ----
  }
  
  /*
+  * shiftArrayResult - shift leading Datums out of an array result
+  *
+  *	astate is working state
+  *	count is the number of leading Datums to shift out
+  *
+  * If count is equal to or larger than the number of relements, the array
+  * result is empty afterwards. If astate is NULL, nothing is done.
+  */
+ void
+ shiftArrayResult(ArrayBuildState *astate, int count)
+ {
+ 	int		i;
+ 
+ 	if (astate == NULL)
+ 		return;
+ 
+ 	/* Limit shift count to number of elements for safety */
+ 	count = Min(count, astate->nelems);
+ 
+ 	/* For pass-by-ref types, free values we shift out */
+ 	if (!astate->typbyval) {
+ 		for(i = astate->offset; i < astate->offset + count; ++i) {
+ 			if (astate->dnulls[i])
+ 				continue;
+ 
+ 			pfree(DatumGetPointer(astate->dvalues[i]));
+ 
+ 			/* For cleanliness' sake */
+ 			astate->dnulls[i] = false;
+ 			astate->dvalues[i] = 0;
+ 		}
+ 
+ 	}
+ 
+ 	/* Update state */
+ 	astate->nelems -= count;
+ 	astate->offset += count;
+ }
+ 
+ 
+ /*
   * makeArrayResult - produce 1-D final result of accumArrayResult
   *
   *	astate is working state (not NULL)
*************** makeMdArrayResult(ArrayBuildState *astat
*** 4679,4686 ****
  	/* Build the final array result in rcontext */
  	oldcontext = MemoryContextSwitchTo(rcontext);
  
! 	result = construct_md_array(astate->dvalues,
! 								astate->dnulls,
  								ndims,
  								dims,
  								lbs,
--- 4735,4742 ----
  	/* Build the final array result in rcontext */
  	oldcontext = MemoryContextSwitchTo(rcontext);
  
! 	result = construct_md_array(astate->dvalues + astate->offset,
! 								astate->dnulls + astate->offset,
  								ndims,
  								dims,
  								lbs,
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index cb07a06..74fe7fc 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** typedef struct
*** 50,55 ****
--- 50,63 ----
  	int			skiptable[256]; /* skip distance for given mismatched char */
  } TextPositionState;
  
+ typedef struct StringAggState
+ {
+ 	StringInfoData 	string; 	/* Contents */
+ 	int 			offset;		/* Offset into stringinfo's data */
+ 	int 			delimLen;	/* Delim length, -1 initially, -2 if multiple */
+ 	int64			notNullCount;/* Number of non-NULL inputs */
+ } StringAggState;
+ 
  #define DatumGetUnknownP(X)			((unknown *) PG_DETOAST_DATUM(X))
  #define DatumGetUnknownPCopy(X)		((unknown *) PG_DETOAST_DATUM_COPY(X))
  #define PG_GETARG_UNKNOWN_P(n)		DatumGetUnknownP(PG_GETARG_DATUM(n))
*************** static void appendStringInfoText(StringI
*** 78,84 ****
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
! static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
  static bool text_format_parse_digits(const char **ptr, const char *end_ptr,
  						 int *value);
  static const char *text_format_parse_format(const char *start_ptr,
--- 86,95 ----
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
! static StringAggState* makeStringAggState(FunctionCallInfo fcinfo);
! static void prepareAppendStringAggState(StringAggState *state,
! 										int delimLen, int valueLen);
! static bool removeFromStringAggState(StringAggState *state, int valueLen);
  static bool text_format_parse_digits(const char **ptr, const char *end_ptr,
  						 int *value);
  static const char *text_format_parse_format(const char *start_ptr,
*************** static void text_format_string_conversio
*** 92,97 ****
--- 103,226 ----
  static void text_format_append_string(StringInfo buf, const char *str,
  						  int flags, int width);
  
+ /*****************************************************************************
+  *	 SUPPORT ROUTINES FOR STRING_AGG(TEXT) AND STRING_AGG(BYTEA)			 *
+  *****************************************************************************/
+ 
+ /*
+  * subroutine to initialize state
+  */
+ static StringAggState*
+ makeStringAggState(FunctionCallInfo fcinfo)
+ {
+ 	StringAggState*	state;
+ 	MemoryContext aggcontext;
+ 	MemoryContext oldcontext;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &aggcontext))
+ 	{
+ 		/* cannot be called directly because of internal-type argument */
+ 		elog(ERROR, "string_agg_transfn called in non-aggregate context");
+ 	}
+ 
+ 	/*
+ 	 * Create state in aggregate context.  It'll stay there across subsequent
+ 	 * calls.
+ 	 */
+ 	oldcontext = MemoryContextSwitchTo(aggcontext);
+ 	state = (StringAggState *) palloc(sizeof(StringAggState));
+ 	initStringInfo(&state->string);
+ 	state->offset = 0;
+ 	state->delimLen = -1;
+ 	state->notNullCount = 0;
+ 	MemoryContextSwitchTo(oldcontext);
+ 
+ 	return state;
+ }
+ 
+ /*
+  * Prepare state for appending a value and a delimiter with specified lengths.
+  * pass -1 for delimLen if no delimiter will be added
+  */
+ void
+ prepareAppendStringAggState(StringAggState *state, int delimLen, int valueLen)
+ {
+ 	/*
+ 	 * Reclaim wasted space
+ 	 *
+ 	 * We move the contents to the left if the current contents fit into the
+ 	 * wasted space, i.e. if we waste more than we store. The limit is
+ 	 * somewhat arbitrary, but it's the smallest one that allows
+ 	 * memcpy to be used, because the source and destination don't overlap.
+ 	 * Note that we must check for <, not <=, because we include the trailing
+ 	 * '\0' in the copy.
+ 	 */
+ 	if (state->string.len - state->offset < state->offset)
+ 	{
+ 		state->string.len -= state->offset;
+ 		memcpy(state->string.data, state->string.data + state->offset,
+ 			   state->string.len + 1);
+ 		state->offset = 0;
+ 	}
+ 
+ 	/*
+ 	 * Enlarge StringInfo
+ 	 *
+ 	 * Not strictly necessary, but avoids potentially resizing twice when
+ 	 * the actual append... calls are done by the caller
+ 	 */
+ 	enlargeStringInfo(&state->string, Max(delimLen, 0) + valueLen);
+ 
+ 
+ 	/* Track delimiter length */
+ 	if (delimLen == -1)
+ 		{} /* Not specified, don't update */
+ 	else if (state->delimLen == -1)
+ 		state->delimLen = delimLen;
+ 	else if (state->delimLen != delimLen)
+ 		state->delimLen = -2;
+ }
+ 
+ /*
+  * Remove value with given length and the delimiter that follows
+  *
+  * Returns false if removal was impossible because delimiters varied
+  */
+ bool
+ removeFromStringAggState(StringAggState *state, int valueLen)
+ {
+ 	/* Remove the string */
+ 	state->offset += valueLen;
+ 
+ 	/*
+ 	 * Remove delimiter if necessary.
+ 	 *
+ 	 * The delimiter we need to remove isn't the delimiter we were passed, but
+ 	 * rather the delimiter passed when adding the input *after* this one. We
+ 	 * thus need the delimiter length to be all the same to be able to proceed.
+ 	 * If we're removing the last string, there will be no delimiter following
+ 	 * it. In that case, we may reset delimLen to its initial value.
+ 	 */
+ 	if (state->delimLen == -2)
+ 		return false;
+ 	if (state->offset < state->string.len)
+ 	{
+ 		Assert(state->delimLen >= 0);
+ 		state->offset += state->delimLen;
+ 	}
+ 	else
+ 		state->delimLen = -1;
+ 
+ 	/* Don't crash if we're ever asked to remove more than was added */
+ 	if (state->offset > state->string.len)
+ 	{
+ 		state->offset = state->string.len;
+ 		elog(ERROR, "tried to remove more data than was aggregated");
+ 	}
+ 
+ 	return true;
+ }
+ 
  
  /*****************************************************************************
   *	 CONVERSION ROUTINES EXPORTED FOR USE BY C CODE							 *
*************** byteasend(PG_FUNCTION_ARGS)
*** 408,435 ****
  Datum
  bytea_string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null. */
! 	if (!PG_ARGISNULL(1))
! 	{
! 		bytea	   *value = PG_GETARG_BYTEA_PP(1);
  
! 		/* On the first time through, we ignore the delimiter. */
! 		if (state == NULL)
! 			state = makeStringAggState(fcinfo);
! 		else if (!PG_ARGISNULL(2))
! 		{
! 			bytea	   *delim = PG_GETARG_BYTEA_PP(2);
  
! 			appendBinaryStringInfo(state, VARDATA_ANY(delim), VARSIZE_ANY_EXHDR(delim));
! 		}
  
! 		appendBinaryStringInfo(state, VARDATA_ANY(value), VARSIZE_ANY_EXHDR(value));
  	}
  
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
--- 537,589 ----
  Datum
  bytea_string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	bytea		   *value,
! 				   *delim;
! 	int				valueLen,
! 					delimLen;
! 	StringAggState *state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/* Ignore NULL values, and skip a possible delimiter too. */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
  
! 	/* Allocate state on first non-NULL value */
! 	if (state == NULL)
! 		state = makeStringAggState(fcinfo);
  
! 	value = PG_GETARG_BYTEA_PP(1);
! 	valueLen = VARSIZE_ANY_EXHDR(value);
! 	state->notNullCount++;
  
! 	Assert(state->offset <= state->string.len);
! 	if (state->offset == state->string.len)
! 	{
! 		/* Buffer is empty, ignore delimiter */
! 		delim = NULL;
! 		delimLen = -1;
! 	}
! 	else if (!PG_ARGISNULL(2))
! 	{
! 		/* Delimiter is non-NULL */
! 		delim = PG_GETARG_BYTEA_PP(2);
! 		delimLen = VARSIZE_ANY_EXHDR(delim);
! 	}
! 	else
! 	{
! 		/* Delimiter is NULL, treat as zero-length string */
! 		delim = NULL;
! 		delimLen = 0;
  	}
  
+ 	/* Append delimiter (if non-NULL) and value */
+ 	prepareAppendStringAggState(state, delimLen, valueLen);
+ 	if (delim)
+ 		appendBinaryStringInfo(&state->string, VARDATA_ANY(delim),
+ 							   VARSIZE_ANY_EXHDR(delim));
+ 	appendBinaryStringInfo(&state->string, VARDATA_ANY(value), valueLen);
+ 
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
*************** bytea_string_agg_transfn(PG_FUNCTION_ARG
*** 438,459 ****
  }
  
  Datum
  bytea_string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	if (state != NULL)
  	{
  		bytea	   *result;
  
! 		result = (bytea *) palloc(state->len + VARHDRSZ);
! 		SET_VARSIZE(result, state->len + VARHDRSZ);
! 		memcpy(VARDATA(result), state->data, state->len);
  		PG_RETURN_BYTEA_P(result);
  	}
  	else
--- 592,661 ----
  }
  
  Datum
+ bytea_string_agg_invtransfn(PG_FUNCTION_ARGS)
+ {
+ 	int				valueLen;
+ 	StringAggState *state;
+ 
+ 	/*
+ 	 * Shouldn't happen, but we cannot mark this function strict, since it
+ 	 * needs to be able to receive a non-NULL string but a NULL delimiter.
+ 	 * Must also prevent direct calls because of the "interal" argument
+ 	 */
+ 	if (PG_ARGISNULL(0))
+ 		elog(ERROR, "string_agg_invtransfn called with NULL state");
+ 	else if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "string_agg_invtransfn called in non-aggregate context");
+ 
+ 	state = (StringAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* We append nothing if the string is NULL, so skip here as well */
+ 	if (PG_ARGISNULL(1))
+ 		PG_RETURN_POINTER(state);
+ 
+ 	/* No need to de-toast value, need only the length */
+ 	valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1));
+ 	Assert(state->notNullCount >= 1);
+ 	state->notNullCount--;
+ 
+ 	/*
+ 	 * Attempt to remove value plus following delimiter if possible, return
+ 	 * NULL otherwise to force an aggregation restart.
+ 	 *
+ 	 * The transition type for string_agg() is declared to be "internal",
+ 	 * which is a pass-by-value type the same size as a pointer.
+ 	 */
+ 	if (removeFromStringAggState(state, valueLen))
+ 		PG_RETURN_POINTER(state);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ Datum
  bytea_string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringAggState *state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/*
! 	 * string_agg() is supposed to return NULL if all inputs were NULL
! 	 *
! 	 * That happens automatically for strict aggregates, but we can't make
! 	 * string_agg() strict because NULL is a valid delimiter value. So we
! 	 * need to keep track of the number of non-NULL 1st arguments ourselves.
! 	 */
! 	if (state != NULL && state->notNullCount > 0)
  	{
+ 		int			resultLen = state->string.len  - state->offset;
  		bytea	   *result;
  
! 		result = (bytea *) palloc(resultLen + VARHDRSZ);
! 		SET_VARSIZE(result, resultLen + VARHDRSZ);
! 		memcpy(VARDATA(result), state->string.data + state->offset, resultLen);
  		PG_RETURN_BYTEA_P(result);
  	}
  	else
*************** pg_column_size(PG_FUNCTION_ARGS)
*** 3734,3802 ****
   * the associated value.
   */
  
! /* subroutine to initialize state */
! static StringInfo
! makeStringAggState(FunctionCallInfo fcinfo)
  {
! 	StringInfo	state;
! 	MemoryContext aggcontext;
! 	MemoryContext oldcontext;
  
! 	if (!AggCheckCallContext(fcinfo, &aggcontext))
  	{
! 		/* cannot be called directly because of internal-type argument */
! 		elog(ERROR, "string_agg_transfn called in non-aggregate context");
  	}
  
  	/*
! 	 * Create state in aggregate context.  It'll stay there across subsequent
! 	 * calls.
  	 */
! 	oldcontext = MemoryContextSwitchTo(aggcontext);
! 	state = makeStringInfo();
! 	MemoryContextSwitchTo(oldcontext);
! 
! 	return state;
  }
  
  Datum
! string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null. */
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* On the first time through, we ignore the delimiter. */
! 		if (state == NULL)
! 			state = makeStringAggState(fcinfo);
! 		else if (!PG_ARGISNULL(2))
! 			appendStringInfoText(state, PG_GETARG_TEXT_PP(2));	/* delimiter */
  
! 		appendStringInfoText(state, PG_GETARG_TEXT_PP(1));		/* value */
! 	}
  
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  Datum
  string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	if (state != NULL)
! 		PG_RETURN_TEXT_P(cstring_to_text_with_len(state->data, state->len));
  	else
  		PG_RETURN_NULL();
  }
--- 3936,4055 ----
   * the associated value.
   */
  
! Datum
! string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	text		   *value,
! 				   *delim;
! 	int				delimLen;
! 	StringAggState *state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
! 
! 	/* Ignore NULL values, and skip a possible delimiter too. */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
! 
! 	/* Allocate state on first non-NULL value */
! 	if (state == NULL)
! 		state = makeStringAggState(fcinfo);
! 
! 	value = PG_GETARG_TEXT_PP(1);
! 	state->notNullCount++;
! 
! 	Assert(state->offset <= state->string.len);
! 	if (state->offset == state->string.len)
  	{
! 		/* Buffer is empty, ignore delimiter */
! 		delim = NULL;
! 		delimLen = -1;
! 	}
! 	else if (!PG_ARGISNULL(2))
! 	{
! 		/* Delimiter is non-NULL */
! 		delim = PG_GETARG_TEXT_PP(2);
! 		delimLen = VARSIZE_ANY_EXHDR(delim);
  	}
+ 	else
+ 	{
+ 		/* Delimiter is NULL, treat as zero-length string */
+ 		delim = NULL;
+ 		delimLen = 0;
+ 	}
+ 
+ 	/* Append delimiter (if non-NULL) and value */
+ 	prepareAppendStringAggState(state, delimLen, VARSIZE_ANY_EXHDR(value));
+ 	if (delim)
+ 		appendStringInfoText(&state->string, delim);
+ 	appendStringInfoText(&state->string, value);
  
  	/*
! 	 * The transition type for string_agg() is declared to be "internal",
! 	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  Datum
! string_agg_invtransfn(PG_FUNCTION_ARGS)
  {
! 	int				valueLen;
! 	StringAggState *state;
  
! 	/*
! 	 * Shouldn't happen, but we cannot mark this function strict, since it
! 	 * needs to be able to receive a non-NULL string but a NULL delimiter.
! 	 * Must also prevent direct calls because of the "interal" argument
! 	 */
! 	if (PG_ARGISNULL(0))
! 		elog(ERROR, "string_agg_invtransfn called with NULL state");
! 	else if (!AggCheckCallContext(fcinfo, NULL))
! 		elog(ERROR, "string_agg_invtransfn called in non-aggregate context");
  
! 	state = (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/* We append nothing if the string is NULL, so skip here as well */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
! 
! 	/* No need to de-toast value, need only the length */
! 	valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1));
! 	Assert(state->notNullCount >= 1);
! 	state->notNullCount--;
  
  	/*
+ 	 * Attempt to remove value plus following delimiter if possible, return
+ 	 * NULL otherwise to force an aggregation restart.
+ 	 *
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	if (removeFromStringAggState(state, valueLen))
! 		PG_RETURN_POINTER(state);
! 	else
! 		PG_RETURN_NULL();
  }
  
  Datum
  string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringAggState *state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/*
! 	 * string_agg() is supposed to return NULL if all inputs were NULL
! 	 *
! 	 * That happens automatically for strict aggregates, but we can't make
! 	 * string_agg() strict because NULL is a valid delimiter value. So we
! 	 * need to keep track of the number of non-NULL 1st arguments ourselves.
! 	 */
! 	if (state != NULL && state->notNullCount > 0)
! 		PG_RETURN_TEXT_P(cstring_to_text_with_len(state->string.data + state->offset,
! 												  state->string.len - state->offset));
  	else
  		PG_RETURN_NULL();
  }
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 3412661..403bb50 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2243	n 0 bitor		-	-					0	
*** 250,262 ****
  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_ ));
--- 250,262 ----
  DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_invtransfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_invtransfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_invtransfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
  DATA(insert ( 3175	n 0 json_agg_transfn		-	json_agg_finalfn		0	2281	0	_null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 334e6b8..efaa281 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 3168 (  array_replace 
*** 875,880 ****
--- 875,882 ----
  DESCR("replace any occurrences of an element in an array");
  DATA(insert OID = 2333 (  array_agg_transfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3585 (  array_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 2334 (  array_agg_finalfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2277 "2281" _null_ _null_ _null_ _null_ array_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 2335 (  array_agg		   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 2277 "2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
*************** DESCR("aggregate final function");
*** 2463,2474 ****
--- 2465,2480 ----
  
  DATA(insert OID = 3535 (  string_agg_transfn		PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3586 (  string_agg_invtransfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 3536 (  string_agg_finalfn		PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 25 "2281" _null_ _null_ _null_ _null_ string_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 3538 (  string_agg				PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("concatenate aggregate input into a string");
  DATA(insert OID = 3543 (  bytea_string_agg_transfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3587 (  bytea_string_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 3544 (  bytea_string_agg_finalfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 17 "2281" _null_ _null_ _null_ _null_ bytea_string_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 3545 (  string_agg				PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 17 "17 17" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 9bbfaae..9a6fc39 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** typedef struct ArrayBuildState
*** 83,88 ****
--- 83,89 ----
  	Datum	   *dvalues;		/* array of accumulated Datums */
  	bool	   *dnulls;			/* array of is-null flags for Datums */
  	int			alen;			/* allocated length of above arrays */
+ 	int			offset;			/* offset of first element in above arrays */
  	int			nelems;			/* number of valid entries in above arrays */
  	Oid			element_type;	/* data type of the Datums */
  	int16		typlen;			/* needed info about datatype */
*************** extern ArrayBuildState *accumArrayResult
*** 255,260 ****
--- 256,262 ----
  				 Datum dvalue, bool disnull,
  				 Oid element_type,
  				 MemoryContext rcontext);
+ extern void shiftArrayResult(ArrayBuildState *astate, int count);
  extern Datum makeArrayResult(ArrayBuildState *astate,
  				MemoryContext rcontext);
  extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims,
*************** extern ArrayType *create_singleton_array
*** 290,295 ****
--- 292,298 ----
  					   int ndims);
  
  extern Datum array_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum array_agg_invtransfn(PG_FUNCTION_ARGS);
  extern Datum array_agg_finalfn(PG_FUNCTION_ARGS);
  
  /*
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 031a43a..173fa71 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum unknownsend(PG_FUNCTION_ARG
*** 812,820 ****
--- 812,822 ----
  extern Datum pg_column_size(PG_FUNCTION_ARGS);
  
  extern Datum bytea_string_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum bytea_string_agg_invtransfn(PG_FUNCTION_ARGS);
  extern Datum bytea_string_agg_finalfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_transfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_finalfn(PG_FUNCTION_ARGS);
+ extern Datum string_agg_invtransfn(PG_FUNCTION_ARGS);
  
  extern Datum text_concat(PG_FUNCTION_ARGS);
  extern Datum text_concat_ws(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 8af3d23..b33f241 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1305,1307 ****
--- 1305,1351 ----
  -- Tests of the collecting inverse transition functions
  --
  --
+ SELECT
+ 	i::text || ':' || COALESCE(s, '-') || ',' ||
+ 					  COALESCE(encode(b, 'hex'), '----') || ',' ||
+ 					  COALESCE(n::text, '-') AS row,
+ 	coalesce(string_agg(s,'')        OVER wnd, '-') AS str,
+ 	coalesce(string_agg(s,',')       OVER wnd, '-') AS str_del,
+ 	coalesce(string_agg(s, nullif(repeat('|', i%3), ''))
+ 			 OVER wnd, '-') AS str_vardel,
+ 	coalesce((string_agg(b,'')        OVER wnd)::text, '-') AS bin,
+ 	coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del,
+ 	coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), ''))
+ 			 OVER wnd)::text, '-') AS bin_vardel,
+ 	array_agg(n) OVER wnd AS ary
+ FROM (VALUES
+ 	(1,  '1',  E'\\x0100'::bytea, NULL::int8),
+ 	(2,  NULL, E'\\x0200'::bytea, 2::int8),
+ 	(3,  '3',  NULL,              3::int8),
+ 	(4,  NULL, E'\\x0400'::bytea, 4::int8),
+ 	(5,  '5',  NULL,              NULL::int8),
+ 	(6,  '6',  E'\\x0600'::bytea, 6::int8),
+ 	(7,  '7',  E'\\x0700'::bytea, NULL::int8),
+ 	(8,  '8',  E'\\x0800'::bytea, 8::int8),
+ 	(9,  NULL, NULL,              NULL),
+ 	(10, NULL, NULL,              NULL),
+ 	(11, NULL, NULL,              NULL),
+ 	(12, NULL, NULL,              NULL)
+ ) AS v(i, s, b, n)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING);
+      row     | str | str_del | str_vardel |      bin       |      bin_del       |      bin_vardel      |       ary        
+ -------------+-----+---------+------------+----------------+--------------------+----------------------+------------------
+  1:1,0100,-  | -   | -       | -          | -              | -                  | -                    | 
+  2:-,0200,2  | 1   | 1       | 1          | \x0100         | \x0100             | \x0100               | {NULL}
+  3:3,----,3  | 1   | 1       | 1          | \x01000200     | \x0100000200       | \x010000000200       | {NULL,2}
+  4:-,0400,4  | 13  | 1,3     | 13         | \x01000200     | \x0100000200       | \x010000000200       | {NULL,2,3}
+  5:5,----,-  | 3   | 3       | 3          | \x02000400     | \x0200000400       | \x0200000400         | {2,3,4}
+  6:6,0600,6  | 35  | 3,5     | 3||5       | \x0400         | \x0400             | \x0400               | {3,4,NULL}
+  7:7,0700,-  | 56  | 5,6     | 56         | \x04000600     | \x0400000600       | \x04000600           | {4,NULL,6}
+  8:8,0800,8  | 567 | 5,6,7   | 56|7       | \x06000700     | \x0600000700       | \x0600000700         | {NULL,6,NULL}
+  9:-,----,-  | 678 | 6,7,8   | 6|7||8     | \x060007000800 | \x0600000700000800 | \x060000070000000800 | {6,NULL,8}
+  10:-,----,- | 78  | 7,8     | 7||8       | \x07000800     | \x0700000800       | \x070000000800       | {NULL,8,NULL}
+  11:-,----,- | 8   | 8       | 8          | \x0800         | \x0800             | \x0800               | {8,NULL,NULL}
+  12:-,----,- | -   | -       | -          | -              | -                  | -                    | {NULL,NULL,NULL}
+ (12 rows)
+ 
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 117bd6c..9bb911c 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 487,489 ****
--- 487,518 ----
  -- Tests of the collecting inverse transition functions
  --
  --
+ 
+ SELECT
+ 	i::text || ':' || COALESCE(s, '-') || ',' ||
+ 					  COALESCE(encode(b, 'hex'), '----') || ',' ||
+ 					  COALESCE(n::text, '-') AS row,
+ 	coalesce(string_agg(s,'')        OVER wnd, '-') AS str,
+ 	coalesce(string_agg(s,',')       OVER wnd, '-') AS str_del,
+ 	coalesce(string_agg(s, nullif(repeat('|', i%3), ''))
+ 			 OVER wnd, '-') AS str_vardel,
+ 	coalesce((string_agg(b,'')        OVER wnd)::text, '-') AS bin,
+ 	coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del,
+ 	coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), ''))
+ 			 OVER wnd)::text, '-') AS bin_vardel,
+ 	array_agg(n) OVER wnd AS ary
+ FROM (VALUES
+ 	(1,  '1',  E'\\x0100'::bytea, NULL::int8),
+ 	(2,  NULL, E'\\x0200'::bytea, 2::int8),
+ 	(3,  '3',  NULL,              3::int8),
+ 	(4,  NULL, E'\\x0400'::bytea, 4::int8),
+ 	(5,  '5',  NULL,              NULL::int8),
+ 	(6,  '6',  E'\\x0600'::bytea, 6::int8),
+ 	(7,  '7',  E'\\x0700'::bytea, NULL::int8),
+ 	(8,  '8',  E'\\x0800'::bytea, 8::int8),
+ 	(9,  NULL, NULL,              NULL),
+ 	(10, NULL, NULL,              NULL),
+ 	(11, NULL, NULL,              NULL),
+ 	(12, NULL, NULL,              NULL)
+ ) AS v(i, s, b, n)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING);
