diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7b1a85f..faa4939 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** NULL baz</literallayout>(3 rows)</entry>
*** 13358,13364 ****
        <entry>
         array of the argument type
        </entry>
!       <entry>No</entry>
        <entry>input values, including nulls, concatenated into an array</entry>
       </row>
  
--- 13358,13364 ----
        <entry>
         array of the argument type
        </entry>
!       <entry>Yes</entry>
        <entry>input values, including nulls, concatenated into an array</entry>
       </row>
  
*************** NULL baz</literallayout>(3 rows)</entry>
*** 13372,13378 ****
        <entry>
         same as argument data type
        </entry>
!       <entry>No</entry>
        <entry>input arrays concatenated into array of one higher dimension
         (inputs must all have same dimensionality,
          and cannot be empty or NULL)</entry>
--- 13372,13378 ----
        <entry>
         same as argument data type
        </entry>
!       <entry>Yes</entry>
        <entry>input arrays concatenated into array of one higher dimension
         (inputs must all have same dimensionality,
          and cannot be empty or NULL)</entry>
*************** NULL baz</literallayout>(3 rows)</entry>
*** 13633,13639 ****
        <entry>
         same as argument types
        </entry>
!       <entry>No</entry>
        <entry>input values concatenated into a string, separated by delimiter</entry>
       </row>
  
--- 13633,13639 ----
        <entry>
         same as argument types
        </entry>
!       <entry>Yes</entry>
        <entry>input values concatenated into a string, separated by delimiter</entry>
       </row>
  
diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c
index cb7a6b6..0007a28 100644
*** a/src/backend/utils/adt/array_userfuncs.c
--- b/src/backend/utils/adt/array_userfuncs.c
***************
*** 13,24 ****
--- 13,44 ----
  #include "postgres.h"
  
  #include "catalog/pg_type.h"
+ #include "libpq/pqformat.h"
  #include "common/int.h"
  #include "utils/array.h"
+ #include "utils/datum.h"
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
  #include "utils/typcache.h"
  
+ /*
+  * SerialIOData
+  *		Used for caching element-type data in array_agg_serialize
+  */
+ typedef struct SerialIOData
+ {
+ 	FmgrInfo	typsend;
+ } SerialIOData;
+ 
+ /*
+  * DeserialIOData
+  *		Used for caching element-type data in array_agg_deserialize
+  */
+ typedef struct DeserialIOData
+ {
+ 	FmgrInfo	typreceive;
+ 	Oid			typioparam;
+ } DeserialIOData;
  
  static Datum array_position_common(FunctionCallInfo fcinfo);
  
*************** array_agg_transfn(PG_FUNCTION_ARGS)
*** 499,504 ****
--- 519,838 ----
  }
  
  Datum
+ array_agg_combine(PG_FUNCTION_ARGS)
+ {
+ 	ArrayBuildState *state1;
+ 	ArrayBuildState *state2;
+ 	MemoryContext agg_context;
+ 	MemoryContext old_context;
+ 	int			i;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &agg_context))
+ 		elog(ERROR, "aggregate function called in non-aggregate context");
+ 
+ 	state1 = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+ 	state2 = PG_ARGISNULL(1) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(1);
+ 
+ 	if (state2 == NULL)
+ 	{
+ 		/*
+ 		 * NULL state2 is easy, just return state1, which we know is already
+ 		 * in the agg_context
+ 		 */
+ 		if (state1 == NULL)
+ 			PG_RETURN_NULL();
+ 		PG_RETURN_POINTER(state1);
+ 	}
+ 
+ 	if (state1 == NULL)
+ 	{
+ 		/* We must copy state2's data into the agg_context */
+ 		state1 = initArrayResultWithSize(state2->element_type, agg_context,
+ 										 false, state2->alen);
+ 
+ 		old_context = MemoryContextSwitchTo(agg_context);
+ 
+ 		for (i = 0; i < state2->nelems; i++)
+ 		{
+ 			if (!state2->dnulls[i])
+ 				state1->dvalues[i] = datumCopy(state2->dvalues[i],
+ 											   state1->typbyval,
+ 											   state1->typlen);
+ 			else
+ 				state1->dvalues[i] = (Datum) 0;
+ 		}
+ 
+ 		MemoryContextSwitchTo(old_context);
+ 
+ 		memcpy(state1->dnulls, state2->dnulls, sizeof(bool) * state2->nelems);
+ 
+ 		state1->nelems = state2->nelems;
+ 
+ 		PG_RETURN_POINTER(state1);
+ 	}
+ 	else if (state2->nelems > 0)
+ 	{
+ 		/* We only need to combine the two states if state2 has any elements */
+ 		int			reqsize = state1->nelems + state2->nelems;
+ 		MemoryContext oldContext = MemoryContextSwitchTo(state1->mcontext);
+ 
+ 		Assert(state1->element_type == state2->element_type);
+ 
+ 		/* Enlarge state1 arrays if needed */
+ 		if (state1->alen < reqsize)
+ 		{
+ 			/* Use a power of 2 size rather than allocating just reqsize */
+ 			while (state1->alen < reqsize)
+ 				state1->alen *= 2;
+ 
+ 			state1->dvalues = (Datum *) repalloc(state1->dvalues,
+ 												 state1->alen * sizeof(Datum));
+ 			state1->dnulls = (bool *) repalloc(state1->dnulls,
+ 											   state1->alen * sizeof(bool));
+ 		}
+ 
+ 		/* Copy in the state2 elements to the end of the state1 arrays */
+ 		for (i = 0; i < state2->nelems; i++)
+ 		{
+ 			if (!state2->dnulls[i])
+ 				state1->dvalues[i + state1->nelems] =
+ 					datumCopy(state2->dvalues[i],
+ 							  state1->typbyval,
+ 							  state1->typlen);
+ 			else
+ 				state1->dvalues[i + state1->nelems] = (Datum) 0;
+ 		}
+ 
+ 		memcpy(&state1->dnulls[state1->nelems], state2->dnulls,
+ 			   sizeof(bool) * state2->nelems);
+ 
+ 		state1->nelems = reqsize;
+ 
+ 		MemoryContextSwitchTo(oldContext);
+ 	}
+ 
+ 	PG_RETURN_POINTER(state1);
+ }
+ 
+ /*
+  * array_agg_serialize
+  *		Serialize ArrayBuildState into bytea.
+  */
+ Datum
+ array_agg_serialize(PG_FUNCTION_ARGS)
+ {
+ 	ArrayBuildState *state;
+ 	StringInfoData buf;
+ 	bytea	   *result;
+ 
+ 	/* cannot be called directly because of internal-type argument */
+ 	Assert(AggCheckCallContext(fcinfo, NULL));
+ 
+ 	state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+ 
+ 	pq_begintypsend(&buf);
+ 
+ 	/*
+ 	 * element_type. Putting this first is more convenient in deserialization
+ 	 */
+ 	pq_sendint32(&buf, state->element_type);
+ 
+ 	/*
+ 	 * nelems -- send first so we know how large to make the dvalues and
+ 	 * dnulls array during deserialization.
+ 	 */
+ 	pq_sendint64(&buf, state->nelems);
+ 
+ 	/* alen can be decided during deserialization */
+ 
+ 	/* typlen */
+ 	pq_sendint16(&buf, state->typlen);
+ 
+ 	/* typbyval */
+ 	pq_sendbyte(&buf, state->typbyval);
+ 
+ 	/* typalign */
+ 	pq_sendbyte(&buf, state->typalign);
+ 
+ 	/* dnulls */
+ 	pq_sendbytes(&buf, (char *) state->dnulls, sizeof(bool) * state->nelems);
+ 
+ 	/*
+ 	 * dvalues.  By agreement with array_agg_deserialize, when the element
+ 	 * type is byval, we just transmit the Datum array as-is, including any
+ 	 * null elements.  For by-ref types, we must invoke the element type's
+ 	 * send function, and we skip null elements (which is why the nulls flags
+ 	 * must be sent first).
+ 	 */
+ 	if (state->typbyval)
+ 		pq_sendbytes(&buf, (char *) state->dvalues,
+ 					 sizeof(Datum) * state->nelems);
+ 	else
+ 	{
+ 		SerialIOData *iodata;
+ 		int			i;
+ 
+ 		/* Avoid repeat catalog lookups for typsend function */
+ 		iodata = (SerialIOData *) fcinfo->flinfo->fn_extra;
+ 		if (iodata == NULL)
+ 		{
+ 			Oid			typsend;
+ 			bool		typisvarlena;
+ 
+ 			iodata = (SerialIOData *)
+ 				MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 								   sizeof(SerialIOData));
+ 			getTypeBinaryOutputInfo(state->element_type, &typsend,
+ 									&typisvarlena);
+ 			fmgr_info_cxt(typsend, &iodata->typsend,
+ 						  fcinfo->flinfo->fn_mcxt);
+ 			fcinfo->flinfo->fn_extra = (void *) iodata;
+ 		}
+ 
+ 		for (i = 0; i < state->nelems; i++)
+ 		{
+ 			bytea	   *outputbytes;
+ 
+ 			if (state->dnulls[i])
+ 				continue;
+ 			outputbytes = SendFunctionCall(&iodata->typsend,
+ 										   state->dvalues[i]);
+ 			pq_sendint32(&buf, VARSIZE(outputbytes) - VARHDRSZ);
+ 			pq_sendbytes(&buf, VARDATA(outputbytes),
+ 						 VARSIZE(outputbytes) - VARHDRSZ);
+ 		}
+ 	}
+ 
+ 	result = pq_endtypsend(&buf);
+ 
+ 	PG_RETURN_BYTEA_P(result);
+ }
+ 
+ Datum
+ array_agg_deserialize(PG_FUNCTION_ARGS)
+ {
+ 	bytea	   *sstate;
+ 	ArrayBuildState *result;
+ 	StringInfoData buf;
+ 	Oid			element_type;
+ 	int64		nelems;
+ 	const char *temp;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate function called in non-aggregate context");
+ 
+ 	sstate = PG_GETARG_BYTEA_PP(0);
+ 
+ 	/*
+ 	 * Copy the bytea into a StringInfo so that we can "receive" it using the
+ 	 * standard recv-function infrastructure.
+ 	 */
+ 	initStringInfo(&buf);
+ 	appendBinaryStringInfo(&buf,
+ 						   VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate));
+ 
+ 	/* element_type */
+ 	element_type = pq_getmsgint(&buf, 4);
+ 
+ 	/* nelems */
+ 	nelems = pq_getmsgint64(&buf);
+ 
+ 	/* Create output ArrayBuildState with the needed number of elements */
+ 	result = initArrayResultWithSize(element_type, CurrentMemoryContext,
+ 									 false, nelems);
+ 	result->nelems = nelems;
+ 
+ 	/* typlen */
+ 	result->typlen = pq_getmsgint(&buf, 2);
+ 
+ 	/* typbyval */
+ 	result->typbyval = pq_getmsgbyte(&buf);
+ 
+ 	/* typalign */
+ 	result->typalign = pq_getmsgbyte(&buf);
+ 
+ 	/* dnulls */
+ 	temp = pq_getmsgbytes(&buf, sizeof(bool) * nelems);
+ 	memcpy(result->dnulls, temp, sizeof(bool) * nelems);
+ 
+ 	/* dvalues --- see comment in array_agg_serialize */
+ 	if (result->typbyval)
+ 	{
+ 		temp = pq_getmsgbytes(&buf, sizeof(Datum) * nelems);
+ 		memcpy(result->dvalues, temp, sizeof(Datum) * nelems);
+ 	}
+ 	else
+ 	{
+ 		DeserialIOData *iodata;
+ 		int			i;
+ 
+ 		/* Avoid repeat catalog lookups for typreceive function */
+ 		iodata = (DeserialIOData *) fcinfo->flinfo->fn_extra;
+ 		if (iodata == NULL)
+ 		{
+ 			Oid			typreceive;
+ 
+ 			iodata = (DeserialIOData *)
+ 				MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 								   sizeof(DeserialIOData));
+ 			getTypeBinaryInputInfo(element_type, &typreceive,
+ 								   &iodata->typioparam);
+ 			fmgr_info_cxt(typreceive, &iodata->typreceive,
+ 						  fcinfo->flinfo->fn_mcxt);
+ 			fcinfo->flinfo->fn_extra = (void *) iodata;
+ 		}
+ 
+ 		for (i = 0; i < nelems; i++)
+ 		{
+ 			int			itemlen;
+ 			StringInfoData elem_buf;
+ 			char		csave;
+ 
+ 			if (result->dnulls[i])
+ 			{
+ 				result->dvalues[i] = (Datum) 0;
+ 				continue;
+ 			}
+ 
+ 			itemlen = pq_getmsgint(&buf, 4);
+ 			if (itemlen < 0 || itemlen > (buf.len - buf.cursor))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+ 						 errmsg("insufficient data left in message")));
+ 
+ 			/*
+ 			 * Rather than copying data around, we just set up a phony
+ 			 * StringInfo pointing to the correct portion of the input buffer.
+ 			 * We assume we can scribble on the input buffer so as to maintain
+ 			 * the convention that StringInfos have a trailing null.
+ 			 */
+ 			elem_buf.data = &buf.data[buf.cursor];
+ 			elem_buf.maxlen = itemlen + 1;
+ 			elem_buf.len = itemlen;
+ 			elem_buf.cursor = 0;
+ 
+ 			buf.cursor += itemlen;
+ 
+ 			csave = buf.data[buf.cursor];
+ 			buf.data[buf.cursor] = '\0';
+ 
+ 			/* Now call the element's receiveproc */
+ 			result->dvalues[i] = ReceiveFunctionCall(&iodata->typreceive,
+ 													 &elem_buf,
+ 													 iodata->typioparam,
+ 													 -1);
+ 
+ 			buf.data[buf.cursor] = csave;
+ 		}
+ 	}
+ 
+ 	pq_getmsgend(&buf);
+ 	pfree(buf.data);
+ 
+ 	PG_RETURN_POINTER(result);
+ }
+ 
+ Datum
  array_agg_finalfn(PG_FUNCTION_ARGS)
  {
  	Datum		result;
*************** array_agg_array_transfn(PG_FUNCTION_ARGS
*** 578,583 ****
--- 912,1216 ----
  }
  
  Datum
+ array_agg_array_combine(PG_FUNCTION_ARGS)
+ {
+ 	ArrayBuildStateArr *state1;
+ 	ArrayBuildStateArr *state2;
+ 	MemoryContext agg_context;
+ 	MemoryContext old_context;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &agg_context))
+ 		elog(ERROR, "aggregate function called in non-aggregate context");
+ 
+ 	state1 = PG_ARGISNULL(0) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
+ 	state2 = PG_ARGISNULL(1) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(1);
+ 
+ 	if (state2 == NULL)
+ 	{
+ 		/*
+ 		 * NULL state2 is easy, just return state1, which we know is already
+ 		 * in the agg_context
+ 		 */
+ 		if (state1 == NULL)
+ 			PG_RETURN_NULL();
+ 		PG_RETURN_POINTER(state1);
+ 	}
+ 
+ 	if (state1 == NULL)
+ 	{
+ 		/* We must copy state2's data into the agg_context */
+ 		old_context = MemoryContextSwitchTo(agg_context);
+ 
+ 		state1 = initArrayResultArr(state2->array_type, InvalidOid,
+ 									agg_context, false);
+ 
+ 		state1->abytes = state2->abytes;
+ 		state1->data = (char *) palloc(state1->abytes);
+ 
+ 		if (state2->nullbitmap)
+ 		{
+ 			int			size = (state2->aitems + 7) / 8;
+ 
+ 			state1->nullbitmap = (bits8 *) palloc(size);
+ 			memcpy(state1->nullbitmap, state2->nullbitmap, size);
+ 		}
+ 
+ 		memcpy(state1->data, state2->data, state2->nbytes);
+ 		state1->nbytes = state2->nbytes;
+ 		state1->aitems = state2->aitems;
+ 		state1->nitems = state2->nitems;
+ 		state1->ndims = state2->ndims;
+ 		memcpy(state1->dims, state2->dims, sizeof(state2->dims));
+ 		memcpy(state1->lbs, state2->lbs, sizeof(state2->lbs));
+ 		state1->array_type = state2->array_type;
+ 		state1->element_type = state2->element_type;
+ 
+ 		MemoryContextSwitchTo(old_context);
+ 
+ 		PG_RETURN_POINTER(state1);
+ 	}
+ 
+ 	/* We only need to combine the two states if state2 has any items */
+ 	else if (state2->nitems > 0)
+ 	{
+ 		MemoryContext oldContext;
+ 		int			reqsize = state1->nbytes + state2->nbytes;
+ 		int			i;
+ 
+ 		/*
+ 		 * Check the states are compatible with each other.  Ensure we use the
+ 		 * same error messages that are listed in accumArrayResultArr so that
+ 		 * the same error is shown as would have been if we'd not used the
+ 		 * combine function for the aggregation.
+ 		 */
+ 		if (state1->ndims != state2->ndims)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ 					 errmsg("cannot accumulate arrays of different dimensionality")));
+ 
+ 		/* Check dimensions match ignoring the first dimension. */
+ 		for (i = 1; i < state1->ndims; i++)
+ 		{
+ 			if (state1->dims[i] != state2->dims[i] || state1->lbs[i] != state2->lbs[i])
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ 						 errmsg("cannot accumulate arrays of different dimensionality")));
+ 		}
+ 
+ 
+ 		oldContext = MemoryContextSwitchTo(state1->mcontext);
+ 
+ 		/*
+ 		 * If there's not enough space in state1 then we'll need to reallocate
+ 		 * more.
+ 		 */
+ 		if (state1->abytes < reqsize)
+ 		{
+ 			/* use a power of 2 size rather than allocating just reqsize */
+ 			while (state1->abytes < reqsize)
+ 				state1->abytes *= 2;
+ 
+ 			state1->data = (char *) repalloc(state1->data, state1->abytes);
+ 		}
+ 
+ 		if (state2->nullbitmap)
+ 		{
+ 			int			newnitems = state1->nitems + state2->nitems;
+ 
+ 			if (state1->nullbitmap == NULL)
+ 			{
+ 				/*
+ 				 * First input with nulls; we must retrospectively handle any
+ 				 * previous inputs by marking all their items non-null.
+ 				 */
+ 				state1->aitems = 256;
+ 				while (state1->aitems <= newnitems)
+ 					state1->aitems *= 2;
+ 				state1->nullbitmap = (bits8 *) palloc((state1->aitems + 7) / 8);
+ 				array_bitmap_copy(state1->nullbitmap, 0,
+ 								  NULL, 0,
+ 								  state1->nitems);
+ 			}
+ 			else if (newnitems > state1->aitems)
+ 			{
+ 				int			newaitems = state1->aitems + state2->aitems;
+ 
+ 				while (state1->aitems < newaitems)
+ 					state1->aitems *= 2;
+ 
+ 				state1->nullbitmap = (bits8 *)
+ 					repalloc(state1->nullbitmap, (state1->aitems + 7) / 8);
+ 			}
+ 			array_bitmap_copy(state1->nullbitmap, state1->nitems,
+ 							  state2->nullbitmap, 0,
+ 							  state2->nitems);
+ 		}
+ 
+ 		memcpy(state1->data + state1->nbytes, state2->data, state2->nbytes);
+ 		state1->nbytes += state2->nbytes;
+ 		state1->nitems += state2->nitems;
+ 
+ 		state1->dims[0] += state2->dims[0];
+ 		/* remaing dims already match, per test above */
+ 
+ 		Assert(state1->array_type == state2->array_type);
+ 		Assert(state1->element_type = state2->element_type);
+ 
+ 		MemoryContextSwitchTo(oldContext);
+ 	}
+ 
+ 	PG_RETURN_POINTER(state1);
+ }
+ 
+ /*
+  * array_agg_array_serialize
+  *		Serialize ArrayBuildStateArr into bytea.
+  */
+ Datum
+ array_agg_array_serialize(PG_FUNCTION_ARGS)
+ {
+ 	ArrayBuildStateArr *state;
+ 	StringInfoData buf;
+ 	bytea	   *result;
+ 
+ 	/* cannot be called directly because of internal-type argument */
+ 	Assert(AggCheckCallContext(fcinfo, NULL));
+ 
+ 	state = (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
+ 
+ 	pq_begintypsend(&buf);
+ 
+ 	/*
+ 	 * element_type. Putting this first is more convenient in deserialization
+ 	 * so that we can init the new state sooner.
+ 	 */
+ 	pq_sendint32(&buf, state->element_type);
+ 
+ 	/* array_type */
+ 	pq_sendint32(&buf, state->array_type);
+ 
+ 	/* nbytes */
+ 	pq_sendint32(&buf, state->nbytes);
+ 
+ 	/* data */
+ 	pq_sendbytes(&buf, state->data, state->nbytes);
+ 
+ 	/* abytes */
+ 	pq_sendint32(&buf, state->abytes);
+ 
+ 	/* aitems */
+ 	pq_sendint32(&buf, state->aitems);
+ 
+ 	/* nullbitmap */
+ 	if (state->nullbitmap)
+ 	{
+ 		Assert(state->aitems > 0);
+ 		pq_sendbytes(&buf, (char *) state->nullbitmap, (state->aitems + 7) / 8);
+ 	}
+ 
+ 	/* nitems */
+ 	pq_sendint32(&buf, state->nitems);
+ 
+ 	/* ndims */
+ 	pq_sendint32(&buf, state->ndims);
+ 
+ 	/* dims: XXX should we just send ndims elements? */
+ 	pq_sendbytes(&buf, (char *) state->dims, sizeof(state->dims));
+ 
+ 	/* lbs */
+ 	pq_sendbytes(&buf, (char *) state->lbs, sizeof(state->lbs));
+ 
+ 	result = pq_endtypsend(&buf);
+ 
+ 	PG_RETURN_BYTEA_P(result);
+ }
+ 
+ Datum
+ array_agg_array_deserialize(PG_FUNCTION_ARGS)
+ {
+ 	bytea	   *sstate;
+ 	ArrayBuildStateArr *result;
+ 	StringInfoData buf;
+ 	Oid			element_type;
+ 	Oid			array_type;
+ 	int			nbytes;
+ 	const char *temp;
+ 
+ 	/* cannot be called directly because of internal-type argument */
+ 	Assert(AggCheckCallContext(fcinfo, NULL));
+ 
+ 	sstate = PG_GETARG_BYTEA_PP(0);
+ 
+ 	/*
+ 	 * Copy the bytea into a StringInfo so that we can "receive" it using the
+ 	 * standard recv-function infrastructure.
+ 	 */
+ 	initStringInfo(&buf);
+ 	appendBinaryStringInfo(&buf,
+ 						   VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate));
+ 
+ 	/* element_type */
+ 	element_type = pq_getmsgint(&buf, 4);
+ 
+ 	/* array_type */
+ 	array_type = pq_getmsgint(&buf, 4);
+ 
+ 	/* nbytes */
+ 	nbytes = pq_getmsgint(&buf, 4);
+ 
+ 	result = initArrayResultArr(array_type, element_type,
+ 								CurrentMemoryContext, false);
+ 
+ 	result->abytes = 1024;
+ 	while (result->abytes < nbytes)
+ 		result->abytes *= 2;
+ 
+ 	result->data = (char *) palloc(result->abytes);
+ 
+ 	/* data */
+ 	temp = pq_getmsgbytes(&buf, nbytes);
+ 	memcpy(result->data, temp, nbytes);
+ 	result->nbytes = nbytes;
+ 
+ 	/* abytes */
+ 	result->abytes = pq_getmsgint(&buf, 4);
+ 
+ 	/* aitems: might be 0 */
+ 	result->aitems = pq_getmsgint(&buf, 4);
+ 
+ 	/* nullbitmap */
+ 	if (result->aitems > 0)
+ 	{
+ 		int			size = (result->aitems + 7) / 8;
+ 
+ 		result->nullbitmap = (bits8 *) palloc(size);
+ 		temp = pq_getmsgbytes(&buf, size);
+ 		memcpy(result->nullbitmap, temp, size);
+ 	}
+ 	else
+ 		result->nullbitmap = NULL;
+ 
+ 	/* nitems */
+ 	result->nitems = pq_getmsgint(&buf, 4);
+ 
+ 	/* ndims */
+ 	result->ndims = pq_getmsgint(&buf, 4);
+ 
+ 	/* dims */
+ 	temp = pq_getmsgbytes(&buf, sizeof(result->dims));
+ 	memcpy(result->dims, temp, sizeof(result->dims));
+ 
+ 	/* lbs */
+ 	temp = pq_getmsgbytes(&buf, sizeof(result->lbs));
+ 	memcpy(result->lbs, temp, sizeof(result->lbs));
+ 
+ 	pq_getmsgend(&buf);
+ 	pfree(buf.data);
+ 
+ 	PG_RETURN_POINTER(result);
+ }
+ 
+ Datum
  array_agg_array_finalfn(PG_FUNCTION_ARGS)
  {
  	Datum		result;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 0cbdbe5..1210704 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** array_insert_slice(ArrayType *destArray,
*** 4996,5007 ****
   * subcontext=false.
   *
   * In cases when the array build states have different lifetimes, using a
!  * single memory context is impractical. Instead, pass subcontext=true so that
!  * the array build states can be freed individually.
   */
  ArrayBuildState *
  initArrayResult(Oid element_type, MemoryContext rcontext, bool subcontext)
  {
  	ArrayBuildState *astate;
  	MemoryContext arr_context = rcontext;
  
--- 4996,5025 ----
   * subcontext=false.
   *
   * In cases when the array build states have different lifetimes, using a
!  * single memory context is impractical.  Instead, pass subcontext=true so
!  * that the array build states can be freed individually.
   */
  ArrayBuildState *
  initArrayResult(Oid element_type, MemoryContext rcontext, bool subcontext)
  {
+ 	/*
+ 	 * When using a subcontext, we can afford to start with a somewhat larger
+ 	 * initial array size.  Without subcontexts, we'd better hope that most of
+ 	 * the states stay small ...
+ 	 */
+ 	return initArrayResultWithSize(element_type, rcontext, subcontext,
+ 								   subcontext ? 64 : 8);
+ }
+ 
+ /*
+  * initArrayResultWithSize
+  *		As initArrayResult, but allow the initial size of the allocated arrays
+  *		to be specified.
+  */
+ ArrayBuildState *
+ initArrayResultWithSize(Oid element_type, MemoryContext rcontext,
+ 						bool subcontext, int initsize)
+ {
  	ArrayBuildState *astate;
  	MemoryContext arr_context = rcontext;
  
*************** initArrayResult(Oid element_type, Memory
*** 5015,5021 ****
  		MemoryContextAlloc(arr_context, sizeof(ArrayBuildState));
  	astate->mcontext = arr_context;
  	astate->private_cxt = subcontext;
! 	astate->alen = (subcontext ? 64 : 8);	/* arbitrary starting array size */
  	astate->dvalues = (Datum *)
  		MemoryContextAlloc(arr_context, astate->alen * sizeof(Datum));
  	astate->dnulls = (bool *)
--- 5033,5039 ----
  		MemoryContextAlloc(arr_context, sizeof(ArrayBuildState));
  	astate->mcontext = arr_context;
  	astate->private_cxt = subcontext;
! 	astate->alen = initsize;
  	astate->dvalues = (Datum *)
  		MemoryContextAlloc(arr_context, astate->alen * sizeof(Datum));
  	astate->dnulls = (bool *)
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 4346410..fff89e7 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** bytea_string_agg_transfn(PG_FUNCTION_ARG
*** 453,481 ****
  
  	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.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  Datum
--- 453,502 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null, preceding it with the delimiter. */
  	if (!PG_ARGISNULL(1))
  	{
  		bytea	   *value = PG_GETARG_BYTEA_PP(1);
+ 		bool		isfirst = false;
  
! 		/*
! 		 * We store the delimiter for each aggregated item, even the first
! 		 * one, though you might think that could be discarded immediately.
! 		 * Otherwise, partial aggregation can't work, because the combine
! 		 * function needs the delimiter for the first item in the second
! 		 * aggregated state.  (IOW, what we now think is the first item might
! 		 * not be first overall.)  The finalfn is responsible for stripping
! 		 * off the first delimiter.  So that it can tell how much to strip, we
! 		 * store the length of the first delimiter in the StringInfo's cursor
! 		 * field, which we don't otherwise need here.
! 		 */
  		if (state == NULL)
+ 		{
  			state = makeStringAggState(fcinfo);
! 			isfirst = true;
! 		}
! 
! 		if (!PG_ARGISNULL(2))
  		{
  			bytea	   *delim = PG_GETARG_BYTEA_PP(2);
  
! 			appendBinaryStringInfo(state, VARDATA_ANY(delim),
! 								   VARSIZE_ANY_EXHDR(delim));
! 			if (isfirst)
! 				state->cursor = 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.
  	 */
! 	if (state)
! 		PG_RETURN_POINTER(state);
! 	PG_RETURN_NULL();
  }
  
  Datum
*************** bytea_string_agg_finalfn(PG_FUNCTION_ARG
*** 490,500 ****
  
  	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
--- 511,523 ----
  
  	if (state != NULL)
  	{
+ 		/* As per comment in transfn, strip data before the cursor position */
  		bytea	   *result;
+ 		int			strippedlen = state->len - state->cursor;
  
! 		result = (bytea *) palloc(strippedlen + VARHDRSZ);
! 		SET_VARSIZE(result, strippedlen + VARHDRSZ);
! 		memcpy(VARDATA(result), &state->data[state->cursor], strippedlen);
  		PG_RETURN_BYTEA_P(result);
  	}
  	else
*************** string_agg_transfn(PG_FUNCTION_ARGS)
*** 4653,4675 ****
  
  	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
--- 4676,4846 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null, preceding it with the delimiter. */
  	if (!PG_ARGISNULL(1))
  	{
! 		text	   *value = PG_GETARG_TEXT_PP(1);
! 		bool		isfirst = false;
! 
! 		/*
! 		 * We store the delimiter for each aggregated item, even the first
! 		 * one, though you might think that could be discarded immediately.
! 		 * Otherwise, partial aggregation can't work, because the combine
! 		 * function needs the delimiter for the first item in the second
! 		 * aggregated state.  (IOW, what we now think is the first item might
! 		 * not be first overall.)  The finalfn is responsible for stripping
! 		 * off the first delimiter.  So that it can tell how much to strip, we
! 		 * store the length of the first delimiter in the StringInfo's cursor
! 		 * field, which we don't otherwise need here.
! 		 */
  		if (state == NULL)
+ 		{
  			state = makeStringAggState(fcinfo);
! 			isfirst = true;
! 		}
  
! 		if (!PG_ARGISNULL(2))
! 		{
! 			text	   *delim = PG_GETARG_TEXT_PP(2);
! 
! 			appendStringInfoText(state, delim);
! 			if (isfirst)
! 				state->cursor = VARSIZE_ANY_EXHDR(delim);
! 		}
! 
! 		appendStringInfoText(state, 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.
  	 */
! 	if (state)
! 		PG_RETURN_POINTER(state);
! 	PG_RETURN_NULL();
! }
! 
! /*
!  * string_agg_combine
!  *		Aggregate combine function for string_agg(text) and string_agg(bytea)
!  */
! Datum
! string_agg_combine(PG_FUNCTION_ARGS)
! {
! 	StringInfo	state1;
! 	StringInfo	state2;
! 	MemoryContext agg_context;
! 
! 	if (!AggCheckCallContext(fcinfo, &agg_context))
! 		elog(ERROR, "aggregate function called in non-aggregate context");
! 
! 	state1 = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
! 	state2 = PG_ARGISNULL(1) ? NULL : (StringInfo) PG_GETARG_POINTER(1);
! 
! 	if (state2 == NULL)
! 	{
! 		/*
! 		 * NULL state2 is easy, just return state1, which we know is already
! 		 * in the agg_context
! 		 */
! 		if (state1 == NULL)
! 			PG_RETURN_NULL();
! 		PG_RETURN_POINTER(state1);
! 	}
! 
! 	if (state1 == NULL)
! 	{
! 		/* We must copy state2's data into the agg_context */
! 		MemoryContext old_context;
! 
! 		old_context = MemoryContextSwitchTo(agg_context);
! 		state1 = makeStringAggState(fcinfo);
! 		appendBinaryStringInfo(state1, state2->data, state2->len);
! 		state1->cursor = state2->cursor;
! 		MemoryContextSwitchTo(old_context);
! 	}
! 	else if (state2->len > 0)
! 	{
! 		/* Combine ... state1->cursor does not change in this case */
! 		appendBinaryStringInfo(state1, state2->data, state2->len);
! 	}
! 
! 	PG_RETURN_POINTER(state1);
! }
! 
! /*
!  * string_agg_serialize
!  *		Aggregate serialize function for string_agg(text) and string_agg(bytea)
!  *
!  * This is strict, so we need not handle NULL input
!  */
! Datum
! string_agg_serialize(PG_FUNCTION_ARGS)
! {
! 	StringInfo	state;
! 	StringInfoData buf;
! 	bytea	   *result;
! 
! 	/* cannot be called directly because of internal-type argument */
! 	Assert(AggCheckCallContext(fcinfo, NULL));
! 
! 	state = (StringInfo) PG_GETARG_POINTER(0);
! 
! 	pq_begintypsend(&buf);
! 
! 	/* cursor */
! 	pq_sendint(&buf, state->cursor, 4);
! 
! 	/* data */
! 	pq_sendbytes(&buf, state->data, state->len);
! 
! 	result = pq_endtypsend(&buf);
! 
! 	PG_RETURN_BYTEA_P(result);
! }
! 
! /*
!  * string_agg_deserialize
!  *		Aggregate deserial function for string_agg(text) and string_agg(bytea)
!  *
!  * This is strict, so we need not handle NULL input
!  */
! Datum
! string_agg_deserialize(PG_FUNCTION_ARGS)
! {
! 	bytea	   *sstate;
! 	StringInfo	result;
! 	StringInfoData buf;
! 	char	   *data;
! 	int			datalen;
! 
! 	/* cannot be called directly because of internal-type argument */
! 	Assert(AggCheckCallContext(fcinfo, NULL));
! 
! 	sstate = PG_GETARG_BYTEA_PP(0);
! 
! 	/*
! 	 * Copy the bytea into a StringInfo so that we can "receive" it using the
! 	 * standard recv-function infrastructure.
! 	 */
! 	initStringInfo(&buf);
! 	appendBinaryStringInfo(&buf,
! 						   VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate));
! 
! 	result = makeStringAggState(fcinfo);
! 
! 	/* cursor */
! 	result->cursor = pq_getmsgint(&buf, 4);
! 
! 	/* data */
! 	datalen = VARSIZE_ANY_EXHDR(sstate) - 4;
! 	data = (char *) pq_getmsgbytes(&buf, datalen);
! 	appendBinaryStringInfo(result, data, datalen);
! 
! 	pq_getmsgend(&buf);
! 	pfree(buf.data);
! 
! 	PG_RETURN_POINTER(result);
  }
  
  Datum
*************** string_agg_finalfn(PG_FUNCTION_ARGS)
*** 4683,4689 ****
  	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();
  }
--- 4854,4864 ----
  	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
  	if (state != NULL)
! 	{
! 		/* As per comment in transfn, strip data before the cursor position */
! 		PG_RETURN_TEXT_P(cstring_to_text_with_len(&state->data[state->cursor],
! 												  state->len - state->cursor));
! 	}
  	else
  		PG_RETURN_NULL();
  }
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 125bb5b..b624f4e 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2243	n 0 bitor		-				bitor
*** 300,313 ****
  DATA(insert ( 2901	n 0 xmlconcat2	-				-		-	-	-				-				-				f f r r 0	142		0	0		0	_null_ _null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn		array_agg_finalfn		-	-	-	-		-				-				t f r r 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 4053	n 0 array_agg_array_transfn array_agg_array_finalfn -	-	-	-		-				-				t f r r 0	2281	0	0		0	_null_ _null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_finalfn	-	-	-	-				-				-				f f r r 0	2281	0	0		0	_null_ _null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_finalfn	-	-	-	-				-				-		f f r r 0	2281	0	0		0	_null_ _null_ ));
  
  /* json */
  DATA(insert ( 3175	n 0 json_agg_transfn	json_agg_finalfn			-	-	-	-				-				-				f f r r 0	2281	0	0		0	_null_ _null_ ));
--- 300,313 ----
  DATA(insert ( 2901	n 0 xmlconcat2	-				-		-	-	-				-				-				f f r r 0	142		0	0		0	_null_ _null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn		array_agg_finalfn		array_agg_combine		array_agg_serialize			array_agg_deserialize		-		-				-				t f r r 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 4053	n 0 array_agg_array_transfn array_agg_array_finalfn array_agg_array_combine	array_agg_array_serialize	array_agg_array_deserialize	-		-				-				t f r r 0	2281	0	0		0	_null_ _null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_finalfn	string_agg_combine	string_agg_serialize	string_agg_deserialize	-				-				-				f f r r 0	2281	0	0		0	_null_ _null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_finalfn	string_agg_combine	string_agg_serialize	string_agg_deserialize	-				-				-		f f r r 0	2281	0	0		0	_null_ _null_ ));
  
  /* json */
  DATA(insert ( 3175	n 0 json_agg_transfn	json_agg_finalfn			-	-	-	-				-				-				f f r r 0	2281	0	0		0	_null_ _null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index bfc9009..84cdc44 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 3168 (  array_replace 
*** 942,953 ****
--- 942,965 ----
  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 i s 2 0 2281 "2281 2776" _null_ _null_ _null_ _null_ _null_ array_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3423 (  array_agg_combine	 PGNSP PGUID 12 1 0 0 0 f f f f f i s 2 0 2281 "2281 2281" _null_ _null_ _null_ _null_ _null_ array_agg_combine _null_ _null_ _null_ ));
+ DESCR("aggregate combine function");
+ DATA(insert OID = 3424 (  array_agg_serialize    PGNSP PGUID 12 1 0 0 0 f f f t f i s 1 0 17 "2281" _null_ _null_ _null_ _null_ _null_ array_agg_serialize _null_ _null_ _null_ ));
+ DESCR("aggregate serial function");
+ DATA(insert OID = 3425 (  array_agg_deserialize	 PGNSP PGUID 12 1 0 0 0 f f f t f i s 2 0 2281 "17 2281" _null_ _null_ _null_ _null_ _null_ array_agg_deserialize _null_ _null_ _null_ ));
+ DESCR("aggregate deserial function");
  DATA(insert OID = 2334 (  array_agg_finalfn   PGNSP PGUID 12 1 0 0 0 f f f f f i s 2 0 2277 "2281 2776" _null_ _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 a f f f f i s 1 0 2277 "2776" _null_ _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("concatenate aggregate input into an array");
  DATA(insert OID = 4051 (  array_agg_array_transfn	PGNSP PGUID 12 1 0 0 0 f f f f f i s 2 0 2281 "2281 2277" _null_ _null_ _null_ _null_ _null_ array_agg_array_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3426 (  array_agg_array_combine	PGNSP PGUID 12 1 0 0 0 f f f f f i s 2 0 2281 "2281 2281" _null_ _null_ _null_ _null_ _null_ array_agg_array_combine _null_ _null_ _null_ ));
+ DESCR("aggregate combine function");
+ DATA(insert OID = 3427 (  array_agg_array_serialize PGNSP PGUID 12 1 0 0 0 f f f t f i s 1 0 17 "2281" _null_ _null_ _null_ _null_ _null_ array_agg_array_serialize _null_ _null_ _null_ ));
+ DESCR("aggregate serial function");
+ DATA(insert OID = 3428 (  array_agg_array_deserialize	PGNSP PGUID 12 1 0 0 0 f f f t f i s 2 0 2281 "17 2281" _null_ _null_ _null_ _null_ _null_ array_agg_array_deserialize _null_ _null_ _null_ ));
+ DESCR("aggregate deserial function");
  DATA(insert OID = 4052 (  array_agg_array_finalfn	PGNSP PGUID 12 1 0 0 0 f f f f f i s 2 0 2277 "2281 2277" _null_ _null_ _null_ _null_ _null_ array_agg_array_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 4053 (  array_agg		   PGNSP PGUID 12 1 0 0 0 a f f f f i s 1 0 2277 "2277" _null_ _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
*************** DESCR("aggregate final function");
*** 2710,2715 ****
--- 2722,2733 ----
  
  DATA(insert OID = 3535 (  string_agg_transfn		PGNSP PGUID 12 1 0 0 0 f f f f f i s 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ _null_ string_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3429 (  string_agg_combine		PGNSP PGUID 12 1 0 0 0 f f f f f i s 2 0 2281 "2281 2281" _null_ _null_ _null_ _null_ _null_ string_agg_combine _null_ _null_ _null_ ));
+ DESCR("aggregate combine function");
+ DATA(insert OID = 3430 (  string_agg_serialize		PGNSP PGUID 12 1 0 0 0 f f f t f i s 1 0 17 "2281" _null_ _null_ _null_ _null_ _null_ string_agg_serialize _null_ _null_ _null_ ));
+ DESCR("aggregate serial function");
+ DATA(insert OID = 3431 (  string_agg_deserialize	PGNSP PGUID 12 1 0 0 0 f f f t f i s 2 0 2281 "17 2281" _null_ _null_ _null_ _null_ _null_ string_agg_deserialize _null_ _null_ _null_ ));
+ DESCR("aggregate deserial function");
  DATA(insert OID = 3536 (  string_agg_finalfn		PGNSP PGUID 12 1 0 0 0 f f f f f i s 1 0 25 "2281" _null_ _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 a f f f f i s 2 0 25 "25 25" _null_ _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index afbb532..f99e05a 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** extern bool array_contains_nulls(ArrayTy
*** 393,398 ****
--- 393,401 ----
  
  extern ArrayBuildState *initArrayResult(Oid element_type,
  				MemoryContext rcontext, bool subcontext);
+ extern ArrayBuildState *initArrayResultWithSize(Oid element_type,
+ 						MemoryContext rcontext,
+ 						bool subcontext, int initsize);
  extern ArrayBuildState *accumArrayResult(ArrayBuildState *astate,
  				 Datum dvalue, bool disnull,
  				 Oid element_type,
