diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 90a3460a7122ab2262cf82e51024776b4214749c..baf81ee0404a3886a4be866173885c356fa5dace 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -12035,7 +12035,7 @@ NULL baz</literallayout>(3 rows)</entry>
        <function>array_agg(<replaceable class="parameter">expression</replaceable>)</function>
       </entry>
       <entry>
-       any
+       any non-array type
       </entry>
       <entry>
        array of the argument type
@@ -12043,6 +12043,21 @@ NULL baz</literallayout>(3 rows)</entry>
       <entry>input values, including nulls, concatenated into an array</entry>
      </row>
 
+     <row>
+      <entry>
+       <function>array_agg(<replaceable class="parameter">expression</replaceable>)</function>
+      </entry>
+      <entry>
+       any array type
+      </entry>
+      <entry>
+       same as argument data type
+      </entry>
+      <entry>input arrays concatenated into array of one higher dimension
+       (inputs must all have same dimensionality,
+        and cannot be empty or NULL)</entry>
+     </row>
+
      <row>
       <entry>
        <indexterm>
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 399ae07075978dc4f336644ad5ca48e33ff25296..6f8b7e8b28e26f74053ddf6cf22049431432dc08 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2239,11 +2239,22 @@ SELECT ARRAY(SELECT oid FROM pg_proc WHERE proname LIKE 'bytea%');
 -----------------------------------------------------------------------
  {2011,1954,1948,1952,1951,1244,1950,2005,1949,1953,2006,31,2412,2413}
 (1 row)
+
+SELECT ARRAY(SELECT ARRAY[i, i*2] FROM generate_series(1,5) AS a(i));
+              array
+----------------------------------
+ {{1,2},{2,4},{3,6},{4,8},{5,10}}
+(1 row)
 </programlisting>
-   The subquery must return a single column. The resulting
+   The subquery must return a single column.
+   If the subquery's output column is of a non-array type, the resulting
    one-dimensional array will have an element for each row in the
    subquery result, with an element type matching that of the
    subquery's output column.
+   If the subquery's output column is of an array type, the result will be
+   an array of the same type but one higher dimension; in this case all
+   the subquery rows must yield arrays of identical dimensionality, else
+   the result would not be rectangular.
   </para>
 
   <para>
diff --git a/doc/src/sgml/xaggr.sgml b/doc/src/sgml/xaggr.sgml
index cc8ec64f235f57f2a113ffd1894493ecbe9e46ed..ef7cff487949b016b071553de5209a712fdd0455 100644
--- a/doc/src/sgml/xaggr.sgml
+++ b/doc/src/sgml/xaggr.sgml
@@ -359,12 +359,12 @@ SELECT attrelid::regclass, array_accum(atttypid::regtype)
    aggregate <function>array_agg</> is equivalent to
 
 <programlisting>
-CREATE FUNCTION array_agg_transfn(internal, anyelement)
+CREATE FUNCTION array_agg_transfn(internal, anynonarray)
   RETURNS internal ...;
-CREATE FUNCTION array_agg_finalfn(internal, anyelement)
+CREATE FUNCTION array_agg_finalfn(internal, anynonarray)
   RETURNS anyarray ...;
 
-CREATE AGGREGATE array_agg (anyelement)
+CREATE AGGREGATE array_agg (anynonarray)
 (
     sfunc = array_agg_transfn,
     stype = internal,
@@ -376,7 +376,7 @@ CREATE AGGREGATE array_agg (anyelement)
    Here, the <literal>finalfunc_extra</> option specifies that the final
    function receives, in addition to the state value, extra dummy
    argument(s) corresponding to the aggregate's input argument(s).
-   The extra <type>anyelement</> argument allows the declaration
+   The extra <type>anynonarray</> argument allows the declaration
    of <function>array_agg_finalfn</> to be valid.
   </para>
 
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 401bad45b59dc96ca9a4d4f1b3b4f1f622320c7f..d9faf2000cd84caac73ec0c920bbbc3abb706295 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -231,7 +231,7 @@ ExecScanSubPlan(SubPlanState *node,
 	bool		found = false;	/* TRUE if got at least one subplan tuple */
 	ListCell   *pvar;
 	ListCell   *l;
-	ArrayBuildState *astate = NULL;
+	ArrayBuildStateAny *astate = NULL;
 
 	/*
 	 * MULTIEXPR subplans, when "executed", just return NULL; but first we
@@ -259,6 +259,11 @@ ExecScanSubPlan(SubPlanState *node,
 		return (Datum) 0;
 	}
 
+	/* Initialize ArrayBuildStateAny in caller's context, if needed */
+	if (subLinkType == ARRAY_SUBLINK)
+		astate = initArrayResultAny(subplan->firstColType,
+									CurrentMemoryContext);
+
 	/*
 	 * We are probably in a short-lived expression-evaluation context. Switch
 	 * to the per-query context for manipulating the child plan's chgParam,
@@ -366,8 +371,8 @@ ExecScanSubPlan(SubPlanState *node,
 			/* stash away current value */
 			Assert(subplan->firstColType == tdesc->attrs[0]->atttypid);
 			dvalue = slot_getattr(slot, 1, &disnull);
-			astate = accumArrayResult(astate, dvalue, disnull,
-									  subplan->firstColType, oldcontext);
+			astate = accumArrayResultAny(astate, dvalue, disnull,
+										 subplan->firstColType, oldcontext);
 			/* keep scanning subplan to collect all values */
 			continue;
 		}
@@ -437,10 +442,7 @@ ExecScanSubPlan(SubPlanState *node,
 	if (subLinkType == ARRAY_SUBLINK)
 	{
 		/* We return the result in the caller's context */
-		if (astate != NULL)
-			result = makeArrayResult(astate, oldcontext);
-		else
-			result = PointerGetDatum(construct_empty_array(subplan->firstColType));
+		result = makeArrayResultAny(astate, oldcontext, true);
 	}
 	else if (!found)
 	{
@@ -951,7 +953,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 	ListCell   *pvar;
 	ListCell   *l;
 	bool		found = false;
-	ArrayBuildState *astate = NULL;
+	ArrayBuildStateAny *astate = NULL;
 
 	if (subLinkType == ANY_SUBLINK ||
 		subLinkType == ALL_SUBLINK)
@@ -959,6 +961,11 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 	if (subLinkType == CTE_SUBLINK)
 		elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
 
+	/* Initialize ArrayBuildStateAny in caller's context, if needed */
+	if (subLinkType == ARRAY_SUBLINK)
+		astate = initArrayResultAny(subplan->firstColType,
+									CurrentMemoryContext);
+
 	/*
 	 * Must switch to per-query memory context.
 	 */
@@ -1018,8 +1025,8 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 			/* stash away current value */
 			Assert(subplan->firstColType == tdesc->attrs[0]->atttypid);
 			dvalue = slot_getattr(slot, 1, &disnull);
-			astate = accumArrayResult(astate, dvalue, disnull,
-									  subplan->firstColType, oldcontext);
+			astate = accumArrayResultAny(astate, dvalue, disnull,
+										 subplan->firstColType, oldcontext);
 			/* keep scanning subplan to collect all values */
 			continue;
 		}
@@ -1072,14 +1079,9 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 		 */
 		if (node->curArray != PointerGetDatum(NULL))
 			pfree(DatumGetPointer(node->curArray));
-		if (astate != NULL)
-			node->curArray = makeArrayResult(astate,
-											 econtext->ecxt_per_query_memory);
-		else
-		{
-			MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-			node->curArray = PointerGetDatum(construct_empty_array(subplan->firstColType));
-		}
+		node->curArray = makeArrayResultAny(astate,
+											econtext->ecxt_per_query_memory,
+											true);
 		prm->execPlan = NULL;
 		prm->value = node->curArray;
 		prm->isnull = false;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 41e973b1236637fa36cff03ac35be8ee781bd1f7..ae857a0cbe97239b40cc241d4ce6d3c6b3946734 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -108,7 +108,7 @@ exprType(const Node *expr)
 					type = exprType((Node *) tent->expr);
 					if (sublink->subLinkType == ARRAY_SUBLINK)
 					{
-						type = get_array_type(type);
+						type = get_promoted_array_type(type);
 						if (!OidIsValid(type))
 							ereport(ERROR,
 									(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -139,7 +139,7 @@ exprType(const Node *expr)
 					type = subplan->firstColType;
 					if (subplan->subLinkType == ARRAY_SUBLINK)
 					{
-						type = get_array_type(type);
+						type = get_promoted_array_type(type);
 						if (!OidIsValid(type))
 							ereport(ERROR,
 									(errcode(ERRCODE_UNDEFINED_OBJECT),
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 42b6d0a2cb9d9b4b60b6df0b5f6802e4c65a08f0..579d021893c3d57148aa24e767cd1088fffd6e94 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -668,7 +668,7 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
 
 		Assert(!te->resjunk);
 		Assert(testexpr == NULL);
-		arraytype = get_array_type(exprType((Node *) te->expr));
+		arraytype = get_promoted_array_type(exprType((Node *) te->expr));
 		if (!OidIsValid(arraytype))
 			elog(ERROR, "could not find array type for datatype %s",
 				 format_type_be(exprType((Node *) te->expr)));
diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c
index 831466dec914946bdd95c73f25418784dbdba427..50ea4d226b779d85764923b04d0884a65e4d638a 100644
--- a/src/backend/utils/adt/array_userfuncs.c
+++ b/src/backend/utils/adt/array_userfuncs.c
@@ -471,7 +471,7 @@ create_singleton_array(FunctionCallInfo fcinfo,
 
 
 /*
- * ARRAY_AGG aggregate function
+ * ARRAY_AGG(anynonarray) aggregate function
  */
 Datum
 array_agg_transfn(PG_FUNCTION_ARGS)
@@ -486,6 +486,12 @@ array_agg_transfn(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("could not determine input data type")));
 
+	/*
+	 * Note: we do not need a run-time check about whether arg1_typeid is a
+	 * valid array element type, because the parser would have verified that
+	 * while resolving the input/result types of this polymorphic aggregate.
+	 */
+
 	if (!AggCheckCallContext(fcinfo, &aggcontext))
 	{
 		/* cannot be called directly because of internal-type argument */
@@ -516,18 +522,13 @@ array_agg_finalfn(PG_FUNCTION_ARGS)
 	int			dims[1];
 	int			lbs[1];
 
-	/*
-	 * Test for null before Asserting we are in right context.  This is to
-	 * avoid possible Assert failure in 8.4beta installations, where it is
-	 * possible for users to create NULL constants of type internal.
-	 */
-	if (PG_ARGISNULL(0))
-		PG_RETURN_NULL();		/* returns null iff no input values */
-
 	/* cannot be called directly because of internal-type argument */
 	Assert(AggCheckCallContext(fcinfo, NULL));
 
-	state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	if (state == NULL)
+		PG_RETURN_NULL();		/* returns null iff no input values */
 
 	dims[0] = state->nelems;
 	lbs[0] = 1;
@@ -544,3 +545,70 @@ array_agg_finalfn(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * ARRAY_AGG(anyarray) aggregate function
+ */
+Datum
+array_agg_array_transfn(PG_FUNCTION_ARGS)
+{
+	Oid			arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	MemoryContext aggcontext;
+	ArrayBuildStateArr *state;
+
+	if (arg1_typeid == InvalidOid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("could not determine input data type")));
+
+	/*
+	 * Note: we do not need a run-time check about whether arg1_typeid is a
+	 * valid array type, because the parser would have verified that while
+	 * resolving the input/result types of this polymorphic aggregate.
+	 */
+
+	if (!AggCheckCallContext(fcinfo, &aggcontext))
+	{
+		/* cannot be called directly because of internal-type argument */
+		elog(ERROR, "array_agg_array_transfn called in non-aggregate context");
+	}
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
+	state = accumArrayResultArr(state,
+								PG_GETARG_DATUM(1),
+								PG_ARGISNULL(1),
+								arg1_typeid,
+								aggcontext);
+
+	/*
+	 * The transition type for array_agg() is declared to be "internal", which
+	 * is a pass-by-value type the same size as a pointer.  So we can safely
+	 * pass the ArrayBuildStateArr pointer through nodeAgg.c's machinations.
+	 */
+	PG_RETURN_POINTER(state);
+}
+
+Datum
+array_agg_array_finalfn(PG_FUNCTION_ARGS)
+{
+	Datum		result;
+	ArrayBuildStateArr *state;
+
+	/* cannot be called directly because of internal-type argument */
+	Assert(AggCheckCallContext(fcinfo, NULL));
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
+
+	if (state == NULL)
+		PG_RETURN_NULL();		/* returns null iff no input values */
+
+	/*
+	 * Make the result.  We cannot release the ArrayBuildStateArr because
+	 * sometimes aggregate final functions are re-executed.  Rather, it is
+	 * nodeAgg.c's responsibility to reset the aggcontext when it's safe to do
+	 * so.
+	 */
+	result = makeArrayResultArr(state, CurrentMemoryContext, false);
+
+	PG_RETURN_DATUM(result);
+}
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 6c8b41d2a913c3e1f52f8601df45f68c1020b34c..743351b95e0abc2a570be15982ef34e3f1730a35 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4573,10 +4573,58 @@ array_insert_slice(ArrayType *destArray,
 						  orignitems - orig_offset);
 }
 
+/*
+ * initArrayResult - initialize an empty ArrayBuildState
+ *
+ *	element_type is the array element type (must be a valid array element type)
+ *	rcontext is where to keep working state
+ *
+ * Note: there are two common schemes for using accumArrayResult().
+ * In the older scheme, you start with a NULL ArrayBuildState pointer, and
+ * call accumArrayResult once per element.  In this scheme you end up with
+ * a NULL pointer if there were no elements, which you need to special-case.
+ * In the newer scheme, call initArrayResult and then call accumArrayResult
+ * once per element.  In this scheme you always end with a non-NULL pointer
+ * that you can pass to makeArrayResult; you get an empty array if there
+ * were no elements.  This is preferred if an empty array is what you want.
+ */
+ArrayBuildState *
+initArrayResult(Oid element_type, MemoryContext rcontext)
+{
+	ArrayBuildState *astate;
+	MemoryContext arr_context;
+
+	/* Make a temporary context to hold all the junk */
+	arr_context = AllocSetContextCreate(rcontext,
+										"accumArrayResult",
+										ALLOCSET_DEFAULT_MINSIZE,
+										ALLOCSET_DEFAULT_INITSIZE,
+										ALLOCSET_DEFAULT_MAXSIZE);
+
+	astate = (ArrayBuildState *)
+		MemoryContextAlloc(arr_context, sizeof(ArrayBuildState));
+	astate->mcontext = arr_context;
+	astate->alen = 64;			/* arbitrary starting array size */
+	astate->dvalues = (Datum *)
+		MemoryContextAlloc(arr_context, astate->alen * sizeof(Datum));
+	astate->dnulls = (bool *)
+		MemoryContextAlloc(arr_context, astate->alen * sizeof(bool));
+	astate->nelems = 0;
+	astate->element_type = element_type;
+	get_typlenbyvalalign(element_type,
+						 &astate->typlen,
+						 &astate->typbyval,
+						 &astate->typalign);
+
+	return astate;
+}
+
 /*
  * accumArrayResult - accumulate one (more) Datum for an array result
  *
- *	astate is working state (NULL on first call)
+ *	astate is working state (can be NULL on first call)
+ *	dvalue/disnull represent the new Datum to append to the array
+ *	element_type is the Datum's type (must be a valid array element type)
  *	rcontext is where to keep working state
  */
 ArrayBuildState *
@@ -4585,45 +4633,28 @@ accumArrayResult(ArrayBuildState *astate,
 				 Oid element_type,
 				 MemoryContext rcontext)
 {
-	MemoryContext arr_context,
-				oldcontext;
+	MemoryContext oldcontext;
 
 	if (astate == NULL)
 	{
 		/* First time through --- initialize */
-
-		/* Make a temporary context to hold all the junk */
-		arr_context = AllocSetContextCreate(rcontext,
-											"accumArrayResult",
-											ALLOCSET_DEFAULT_MINSIZE,
-											ALLOCSET_DEFAULT_INITSIZE,
-											ALLOCSET_DEFAULT_MAXSIZE);
-		oldcontext = MemoryContextSwitchTo(arr_context);
-		astate = (ArrayBuildState *) palloc(sizeof(ArrayBuildState));
-		astate->mcontext = arr_context;
-		astate->alen = 64;		/* arbitrary starting array size */
-		astate->dvalues = (Datum *) palloc(astate->alen * sizeof(Datum));
-		astate->dnulls = (bool *) palloc(astate->alen * sizeof(bool));
-		astate->nelems = 0;
-		astate->element_type = element_type;
-		get_typlenbyvalalign(element_type,
-							 &astate->typlen,
-							 &astate->typbyval,
-							 &astate->typalign);
+		astate = initArrayResult(element_type, rcontext);
 	}
 	else
 	{
-		oldcontext = MemoryContextSwitchTo(astate->mcontext);
 		Assert(astate->element_type == element_type);
-		/* enlarge dvalues[]/dnulls[] if needed */
-		if (astate->nelems >= astate->alen)
-		{
-			astate->alen *= 2;
-			astate->dvalues = (Datum *)
-				repalloc(astate->dvalues, astate->alen * sizeof(Datum));
-			astate->dnulls = (bool *)
-				repalloc(astate->dnulls, astate->alen * sizeof(bool));
-		}
+	}
+
+	oldcontext = MemoryContextSwitchTo(astate->mcontext);
+
+	/* enlarge dvalues[]/dnulls[] if needed */
+	if (astate->nelems >= astate->alen)
+	{
+		astate->alen *= 2;
+		astate->dvalues = (Datum *)
+			repalloc(astate->dvalues, astate->alen * sizeof(Datum));
+		astate->dnulls = (bool *)
+			repalloc(astate->dnulls, astate->alen * sizeof(bool));
 	}
 
 	/*
@@ -4654,20 +4685,23 @@ accumArrayResult(ArrayBuildState *astate,
 /*
  * makeArrayResult - produce 1-D final result of accumArrayResult
  *
- *	astate is working state (not NULL)
+ *	astate is working state (must not be NULL)
  *	rcontext is where to construct result
  */
 Datum
 makeArrayResult(ArrayBuildState *astate,
 				MemoryContext rcontext)
 {
+	int			ndims;
 	int			dims[1];
 	int			lbs[1];
 
+	/* If no elements were presented, we want to create an empty array */
+	ndims = (astate->nelems > 0) ? 1 : 0;
 	dims[0] = astate->nelems;
 	lbs[0] = 1;
 
-	return makeMdArrayResult(astate, 1, dims, lbs, rcontext, true);
+	return makeMdArrayResult(astate, ndims, dims, lbs, rcontext, true);
 }
 
 /*
@@ -4676,7 +4710,7 @@ makeArrayResult(ArrayBuildState *astate,
  * beware: no check that specified dimensions match the number of values
  * accumulated.
  *
- *	astate is working state (not NULL)
+ *	astate is working state (must not be NULL)
  *	rcontext is where to construct result
  *	release is true if okay to release working state
  */
@@ -4713,6 +4747,397 @@ makeMdArrayResult(ArrayBuildState *astate,
 	return PointerGetDatum(result);
 }
 
+/*
+ * The following three functions provide essentially the same API as
+ * initArrayResult/accumArrayResult/makeArrayResult, but instead of accepting
+ * inputs that are array elements, they accept inputs that are arrays and
+ * produce an output array having N+1 dimensions.  The inputs must all have
+ * identical dimensionality as well as element type.
+ */
+
+/*
+ * initArrayResultArr - initialize an empty ArrayBuildStateArr
+ *
+ *	array_type is the array type (must be a valid varlena array type)
+ *	element_type is the type of the array's elements
+ *	rcontext is where to keep working state
+ */
+ArrayBuildStateArr *
+initArrayResultArr(Oid array_type, Oid element_type, MemoryContext rcontext)
+{
+	ArrayBuildStateArr *astate;
+	MemoryContext arr_context;
+
+	/* Make a temporary context to hold all the junk */
+	arr_context = AllocSetContextCreate(rcontext,
+										"accumArrayResultArr",
+										ALLOCSET_DEFAULT_MINSIZE,
+										ALLOCSET_DEFAULT_INITSIZE,
+										ALLOCSET_DEFAULT_MAXSIZE);
+
+	/* Note we initialize all fields to zero */
+	astate = (ArrayBuildStateArr *)
+		MemoryContextAllocZero(arr_context, sizeof(ArrayBuildStateArr));
+	astate->mcontext = arr_context;
+
+	/* Save relevant datatype information */
+	astate->array_type = array_type;
+	astate->element_type = element_type;
+
+	return astate;
+}
+
+/*
+ * accumArrayResultArr - accumulate one (more) sub-array for an array result
+ *
+ *	astate is working state (can be NULL on first call)
+ *	dvalue/disnull represent the new sub-array to append to the array
+ *	array_type is the array type (must be a valid varlena array type)
+ *	rcontext is where to keep working state
+ */
+ArrayBuildStateArr *
+accumArrayResultArr(ArrayBuildStateArr *astate,
+					Datum dvalue, bool disnull,
+					Oid array_type,
+					MemoryContext rcontext)
+{
+	ArrayType  *arg;
+	MemoryContext oldcontext;
+	int		   *dims,
+			   *lbs,
+				ndims,
+				nitems,
+				ndatabytes;
+	char	   *data;
+	int			i;
+
+	/*
+	 * We disallow accumulating null subarrays.  Another plausible definition
+	 * is to ignore them, but callers that want that can just skip calling
+	 * this function.
+	 */
+	if (disnull)
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("cannot accumulate null arrays")));
+
+	/* Detoast input array in caller's context */
+	arg = DatumGetArrayTypeP(dvalue);
+
+	if (astate == NULL)
+	{
+		/* First time through --- initialize */
+		Oid			element_type = get_element_type(array_type);
+
+		if (!OidIsValid(element_type))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("data type %s is not an array type",
+							format_type_be(array_type))));
+		astate = initArrayResultArr(array_type, element_type, rcontext);
+	}
+	else
+	{
+		Assert(astate->array_type == array_type);
+	}
+
+	oldcontext = MemoryContextSwitchTo(astate->mcontext);
+
+	/* Collect this input's dimensions */
+	ndims = ARR_NDIM(arg);
+	dims = ARR_DIMS(arg);
+	lbs = ARR_LBOUND(arg);
+	data = ARR_DATA_PTR(arg);
+	nitems = ArrayGetNItems(ndims, dims);
+	ndatabytes = ARR_SIZE(arg) - ARR_DATA_OFFSET(arg);
+
+	if (astate->ndims == 0)
+	{
+		/* First input; check/save the dimensionality info */
+
+		/* Should we allow empty inputs and just produce an empty output? */
+		if (ndims == 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+					 errmsg("cannot accumulate empty arrays")));
+		if (ndims + 1 > MAXDIM)
+			ereport(ERROR,
+					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+					 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
+							ndims + 1, MAXDIM)));
+
+		/*
+		 * The output array will have n+1 dimensions, with the ones after the
+		 * first matching the input's dimensions.
+		 */
+		astate->ndims = ndims + 1;
+		astate->dims[0] = 0;
+		memcpy(&astate->dims[1], dims, ndims * sizeof(int));
+		astate->lbs[0] = 1;
+		memcpy(&astate->lbs[1], lbs, ndims * sizeof(int));
+
+		/* Allocate at least enough data space for this item */
+		astate->abytes = 1024;
+		while (astate->abytes <= ndatabytes)
+			astate->abytes *= 2;
+		astate->data = (char *) palloc(astate->abytes);
+	}
+	else
+	{
+		/* Second or later input: must match first input's dimensionality */
+		if (astate->ndims != ndims + 1)
+			ereport(ERROR,
+					(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+			errmsg("cannot accumulate arrays of different dimensionality")));
+		for (i = 0; i < ndims; i++)
+		{
+			if (astate->dims[i + 1] != dims[i] || astate->lbs[i + 1] != lbs[i])
+				ereport(ERROR,
+						(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+						 errmsg("cannot accumulate arrays of different dimensionality")));
+		}
+
+		/* Enlarge data space if needed */
+		if (astate->nbytes + ndatabytes > astate->abytes)
+		{
+			astate->abytes = Max(astate->abytes * 2,
+								 astate->nbytes + ndatabytes);
+			astate->data = (char *) repalloc(astate->data, astate->abytes);
+		}
+	}
+
+	/*
+	 * Copy the data portion of the sub-array.  Note we assume that the
+	 * advertised data length of the sub-array is properly aligned.  We do not
+	 * have to worry about detoasting elements since whatever's in the
+	 * sub-array should be OK already.
+	 */
+	memcpy(astate->data + astate->nbytes, data, ndatabytes);
+	astate->nbytes += ndatabytes;
+
+	/* Deal with null bitmap if needed */
+	if (astate->nullbitmap || ARR_HASNULL(arg))
+	{
+		int			newnitems = astate->nitems + nitems;
+
+		if (astate->nullbitmap == NULL)
+		{
+			/*
+			 * First input with nulls; we must retrospectively handle any
+			 * previous inputs by marking all their items non-null.
+			 */
+			astate->aitems = 256;
+			while (astate->aitems <= newnitems)
+				astate->aitems *= 2;
+			astate->nullbitmap = (bits8 *) palloc((astate->aitems + 7) / 8);
+			array_bitmap_copy(astate->nullbitmap, 0,
+							  NULL, 0,
+							  astate->nitems);
+		}
+		else if (newnitems > astate->aitems)
+		{
+			astate->aitems = Max(astate->aitems * 2, newnitems);
+			astate->nullbitmap = (bits8 *)
+				repalloc(astate->nullbitmap, (astate->aitems + 7) / 8);
+		}
+		array_bitmap_copy(astate->nullbitmap, astate->nitems,
+						  ARR_NULLBITMAP(arg), 0,
+						  nitems);
+	}
+
+	astate->nitems += nitems;
+	astate->dims[0] += 1;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Release detoasted copy if any */
+	if ((Pointer) arg != DatumGetPointer(dvalue))
+		pfree(arg);
+
+	return astate;
+}
+
+/*
+ * makeArrayResultArr - produce N+1-D final result of accumArrayResultArr
+ *
+ *	astate is working state (must not be NULL)
+ *	rcontext is where to construct result
+ *	release is true if okay to release working state
+ */
+Datum
+makeArrayResultArr(ArrayBuildStateArr *astate,
+				   MemoryContext rcontext,
+				   bool release)
+{
+	ArrayType  *result;
+	MemoryContext oldcontext;
+
+	/* Build the final array result in rcontext */
+	oldcontext = MemoryContextSwitchTo(rcontext);
+
+	if (astate->ndims == 0)
+	{
+		/* No inputs, return empty array */
+		result = construct_empty_array(astate->element_type);
+	}
+	else
+	{
+		int			dataoffset,
+					nbytes;
+
+		/* Compute required space */
+		nbytes = astate->nbytes;
+		if (astate->nullbitmap != NULL)
+		{
+			dataoffset = ARR_OVERHEAD_WITHNULLS(astate->ndims, astate->nitems);
+			nbytes += dataoffset;
+		}
+		else
+		{
+			dataoffset = 0;
+			nbytes += ARR_OVERHEAD_NONULLS(astate->ndims);
+		}
+
+		result = (ArrayType *) palloc0(nbytes);
+		SET_VARSIZE(result, nbytes);
+		result->ndim = astate->ndims;
+		result->dataoffset = dataoffset;
+		result->elemtype = astate->element_type;
+
+		memcpy(ARR_DIMS(result), astate->dims, astate->ndims * sizeof(int));
+		memcpy(ARR_LBOUND(result), astate->lbs, astate->ndims * sizeof(int));
+		memcpy(ARR_DATA_PTR(result), astate->data, astate->nbytes);
+
+		if (astate->nullbitmap != NULL)
+			array_bitmap_copy(ARR_NULLBITMAP(result), 0,
+							  astate->nullbitmap, 0,
+							  astate->nitems);
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Clean up all the junk */
+	if (release)
+		MemoryContextDelete(astate->mcontext);
+
+	return PointerGetDatum(result);
+}
+
+/*
+ * The following three functions provide essentially the same API as
+ * initArrayResult/accumArrayResult/makeArrayResult, but can accept either
+ * scalar or array inputs, invoking the appropriate set of functions above.
+ */
+
+/*
+ * initArrayResultAny - initialize an empty ArrayBuildStateAny
+ *
+ *	input_type is the input datatype (either element or array type)
+ *	rcontext is where to keep working state
+ */
+ArrayBuildStateAny *
+initArrayResultAny(Oid input_type, MemoryContext rcontext)
+{
+	ArrayBuildStateAny *astate;
+	Oid			element_type = get_element_type(input_type);
+
+	if (OidIsValid(element_type))
+	{
+		/* Array case */
+		ArrayBuildStateArr *arraystate;
+
+		arraystate = initArrayResultArr(input_type, element_type, rcontext);
+		astate = (ArrayBuildStateAny *)
+			MemoryContextAlloc(arraystate->mcontext,
+							   sizeof(ArrayBuildStateAny));
+		astate->scalarstate = NULL;
+		astate->arraystate = arraystate;
+	}
+	else
+	{
+		/* Scalar case */
+		ArrayBuildState *scalarstate;
+
+		/* Let's just check that we have a type that can be put into arrays */
+		Assert(OidIsValid(get_array_type(input_type)));
+
+		scalarstate = initArrayResult(input_type, rcontext);
+		astate = (ArrayBuildStateAny *)
+			MemoryContextAlloc(scalarstate->mcontext,
+							   sizeof(ArrayBuildStateAny));
+		astate->scalarstate = scalarstate;
+		astate->arraystate = NULL;
+	}
+
+	return astate;
+}
+
+/*
+ * accumArrayResultAny - accumulate one (more) input for an array result
+ *
+ *	astate is working state (can be NULL on first call)
+ *	dvalue/disnull represent the new input to append to the array
+ *	input_type is the input datatype (either element or array type)
+ *	rcontext is where to keep working state
+ */
+ArrayBuildStateAny *
+accumArrayResultAny(ArrayBuildStateAny *astate,
+					Datum dvalue, bool disnull,
+					Oid input_type,
+					MemoryContext rcontext)
+{
+	if (astate == NULL)
+		astate = initArrayResultAny(input_type, rcontext);
+
+	if (astate->scalarstate)
+		(void) accumArrayResult(astate->scalarstate,
+								dvalue, disnull,
+								input_type, rcontext);
+	else
+		(void) accumArrayResultArr(astate->arraystate,
+								   dvalue, disnull,
+								   input_type, rcontext);
+
+	return astate;
+}
+
+/*
+ * makeArrayResultAny - produce final result of accumArrayResultAny
+ *
+ *	astate is working state (must not be NULL)
+ *	rcontext is where to construct result
+ *	release is true if okay to release working state
+ */
+Datum
+makeArrayResultAny(ArrayBuildStateAny *astate,
+				   MemoryContext rcontext, bool release)
+{
+	Datum		result;
+
+	if (astate->scalarstate)
+	{
+		/* Must use makeMdArrayResult to support "release" parameter */
+		int			ndims;
+		int			dims[1];
+		int			lbs[1];
+
+		/* If no elements were presented, we want to create an empty array */
+		ndims = (astate->scalarstate->nelems > 0) ? 1 : 0;
+		dims[0] = astate->scalarstate->nelems;
+		lbs[0] = 1;
+
+		result = makeMdArrayResult(astate->scalarstate, ndims, dims, lbs,
+								   rcontext, release);
+	}
+	else
+	{
+		result = makeArrayResultArr(astate->arraystate,
+									rcontext, release);
+	}
+	return result;
+}
+
+
 Datum
 array_larger(PG_FUNCTION_ARGS)
 {
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 119dfc7efecc6990cde092982bac366cc604043d..7d903120e5b56d17cb7547b784b59436bd6c2c16 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -143,7 +143,7 @@ static xmlDocPtr xml_parse(text *data, XmlOptionType xmloption_arg,
 		  bool preserve_whitespace, int encoding);
 static text *xml_xmlnodetoxmltype(xmlNodePtr cur);
 static int xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
-					   ArrayBuildState **astate);
+					   ArrayBuildState *astate);
 #endif   /* USE_LIBXML */
 
 static StringInfo query_to_xml_internal(const char *query, char *tablename,
@@ -3648,7 +3648,7 @@ xml_xmlnodetoxmltype(xmlNodePtr cur)
 
 /*
  * Convert an XML XPath object (the result of evaluating an XPath expression)
- * to an array of xml values, which is returned at *astate.  The function
+ * to an array of xml values, which are appended to astate.  The function
  * result value is the number of elements in the array.
  *
  * If "astate" is NULL then we don't generate the array value, but we still
@@ -3660,16 +3660,13 @@ xml_xmlnodetoxmltype(xmlNodePtr cur)
  */
 static int
 xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
-					   ArrayBuildState **astate)
+					   ArrayBuildState *astate)
 {
 	int			result = 0;
 	Datum		datum;
 	Oid			datumtype;
 	char	   *result_str;
 
-	if (astate != NULL)
-		*astate = NULL;
-
 	switch (xpathobj->type)
 	{
 		case XPATH_NODESET:
@@ -3683,9 +3680,8 @@ xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
 					for (i = 0; i < result; i++)
 					{
 						datum = PointerGetDatum(xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i]));
-						*astate = accumArrayResult(*astate, datum,
-												   false, XMLOID,
-												   CurrentMemoryContext);
+						(void) accumArrayResult(astate, datum, false,
+												XMLOID, CurrentMemoryContext);
 					}
 				}
 			}
@@ -3721,9 +3717,8 @@ xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
 	/* Common code for scalar-value cases */
 	result_str = map_sql_value_to_xml_value(datum, datumtype, true);
 	datum = PointerGetDatum(cstring_to_xmltype(result_str));
-	*astate = accumArrayResult(*astate, datum,
-							   false, XMLOID,
-							   CurrentMemoryContext);
+	(void) accumArrayResult(astate, datum, false,
+							XMLOID, CurrentMemoryContext);
 	return 1;
 }
 
@@ -3741,7 +3736,7 @@ xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
  */
 static void
 xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
-			   int *res_nitems, ArrayBuildState **astate)
+			   int *res_nitems, ArrayBuildState *astate)
 {
 	PgXmlErrorContext *xmlerrcxt;
 	volatile xmlParserCtxtPtr ctxt = NULL;
@@ -3933,16 +3928,12 @@ xpath(PG_FUNCTION_ARGS)
 	text	   *xpath_expr_text = PG_GETARG_TEXT_P(0);
 	xmltype    *data = PG_GETARG_XML_P(1);
 	ArrayType  *namespaces = PG_GETARG_ARRAYTYPE_P(2);
-	int			res_nitems;
 	ArrayBuildState *astate;
 
+	astate = initArrayResult(XMLOID, CurrentMemoryContext);
 	xpath_internal(xpath_expr_text, data, namespaces,
-				   &res_nitems, &astate);
-
-	if (res_nitems == 0)
-		PG_RETURN_ARRAYTYPE_P(construct_empty_array(XMLOID));
-	else
-		PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate, CurrentMemoryContext));
+				   NULL, astate);
+	PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate, CurrentMemoryContext));
 #else
 	NO_XML_SUPPORT();
 	return 0;
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 552e498cf57b636e0e545c6cd4490a71470f7054..73138e0a5bb2669f276e335c276563cc2ea8d3cf 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2354,6 +2354,27 @@ get_array_type(Oid typid)
 	return result;
 }
 
+/*
+ * get_promoted_array_type
+ *
+ *		The "promoted" type is what you'd get from an ARRAY(SELECT ...)
+ *		construct, that is, either the corresponding "true" array type
+ *		if the input is a scalar type that has such an array type,
+ *		or the same type if the input is already a "true" array type.
+ *		Returns InvalidOid if neither rule is satisfied.
+ */
+Oid
+get_promoted_array_type(Oid typid)
+{
+	Oid			array_type = get_array_type(typid);
+
+	if (OidIsValid(array_type))
+		return array_type;
+	if (OidIsValid(get_element_type(typid)))
+		return typid;
+	return InvalidOid;
+}
+
 /*
  * get_base_element_type
  *		Given the type OID, get the typelem, looking "through" any domain
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index bdaf4176f6a0ae45ca028b1aa9992d9bde354691..b670902b7d22eb24b97973b5bca43dcdbafe3030 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201411111
+#define CATALOG_VERSION_NO	201411251
 
 #endif
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 3ba9e5e1b268518695b30672a181a8d3a9dfc8ff..327935376162275b0bb037c38b88a3a1888a72f3 100644
--- a/src/include/catalog/pg_aggregate.h
+++ b/src/include/catalog/pg_aggregate.h
@@ -275,6 +275,7 @@ DATA(insert ( 2901	n 0 xmlconcat2	-					-				-				-				f f 0	142		0	0		0	_null_
 
 /* array */
 DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_finalfn	-				-				-				t f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 4053	n 0 array_agg_array_transfn array_agg_array_finalfn -		-				-				t f 0	2281	0	0		0	_null_ _null_ ));
 
 /* text */
 DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_finalfn	-				-				-				f f 0	2281	0	0		0	_null_ _null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 5d4e889af455d61c20d3586d96c9c40a3499506e..56399ac5565d829e6dc0bed2708c8eb6de023aaf 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -565,11 +565,11 @@ DESCR("btree(internal)");
 DATA(insert OID = 2785 (  btoptions		   PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 17 "1009 16" _null_ _null_ _null_ _null_  btoptions _null_ _null_ _null_ ));
 DESCR("btree(internal)");
 
-DATA(insert OID = 3789 (  bringetbitmap	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_	bringetbitmap _null_ _null_ _null_ ));
+DATA(insert OID = 3789 (  bringetbitmap    PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_	bringetbitmap _null_ _null_ _null_ ));
 DESCR("brin(internal)");
 DATA(insert OID = 3790 (  brininsert		   PGNSP PGUID 12 1 0 0 0 f f f f t f v 6 0 16 "2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_	brininsert _null_ _null_ _null_ ));
 DESCR("brin(internal)");
-DATA(insert OID = 3791 (  brinbeginscan	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_	brinbeginscan _null_ _null_ _null_ ));
+DATA(insert OID = 3791 (  brinbeginscan    PGNSP PGUID 12 1 0 0 0 f f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_	brinbeginscan _null_ _null_ _null_ ));
 DESCR("brin(internal)");
 DATA(insert OID = 3792 (  brinrescan		   PGNSP PGUID 12 1 0 0 0 f f f f t f v 5 0 2278 "2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ brinrescan _null_ _null_ _null_ ));
 DESCR("brin(internal)");
@@ -587,7 +587,7 @@ DATA(insert OID = 3798 (  brinbulkdelete	   PGNSP PGUID 12 1 0 0 0 f f f f t f v
 DESCR("brin(internal)");
 DATA(insert OID = 3799 (  brinvacuumcleanup   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 2281 "2281 2281" _null_ _null_ _null_ _null_ brinvacuumcleanup _null_ _null_ _null_ ));
 DESCR("brin(internal)");
-DATA(insert OID = 3800 (  brincostestimate   PGNSP PGUID 12 1 0 0 0 f f f f t f v 7 0 2278 "2281 2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ brincostestimate _null_ _null_ _null_ ));
+DATA(insert OID = 3800 (  brincostestimate	 PGNSP PGUID 12 1 0 0 0 f f f f t f v 7 0 2278 "2281 2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ brincostestimate _null_ _null_ _null_ ));
 DESCR("brin(internal)");
 DATA(insert OID = 3801 (  brinoptions		   PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 17 "1009 16" _null_ _null_ _null_ _null_  brinoptions _null_ _null_ _null_ ));
 DESCR("brin(internal)");
@@ -908,11 +908,17 @@ DATA(insert OID = 3167 (  array_remove	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2
 DESCR("remove any occurrences of an element from an array");
 DATA(insert OID = 3168 (  array_replace    PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2277 "2277 2283 2283" _null_ _null_ _null_ _null_ array_replace _null_ _null_ _null_ ));
 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_ ));
+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 2776" _null_ _null_ _null_ _null_ array_agg_transfn _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 2 0 2277 "2281 2283" _null_ _null_ _null_ _null_ array_agg_finalfn _null_ _null_ _null_ ));
+DATA(insert OID = 2334 (  array_agg_finalfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2277 "2281 2776" _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_ ));
+DATA(insert OID = 2335 (  array_agg		   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 2277 "2776" _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 f i 2 0 2281 "2281 2277" _null_ _null_ _null_ _null_ array_agg_array_transfn _null_ _null_ _null_ ));
+DESCR("aggregate transition function");
+DATA(insert OID = 4052 (  array_agg_array_finalfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2277 "2281 2277" _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 t f f f f f i 1 0 2277 "2277" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
 DESCR("concatenate aggregate input into an array");
 DATA(insert OID = 3218 ( width_bucket	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "2283 2277" _null_ _null_ _null_ _null_ width_bucket_array _null_ _null_ _null_ ));
 DESCR("bucket number of operand given a sorted array of bucket lower bounds");
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index e744314c51bfe5e83d5b2b9156318d1854245994..e1d57490168f282a04e627b30c3a9e649c193bd9 100644
--- a/src/include/utils/array.h
+++ b/src/include/utils/array.h
@@ -76,6 +76,7 @@ typedef struct
 
 /*
  * working state for accumArrayResult() and friends
+ * note that the input must be scalars (legal array elements)
  */
 typedef struct ArrayBuildState
 {
@@ -90,6 +91,37 @@ typedef struct ArrayBuildState
 	char		typalign;
 } ArrayBuildState;
 
+/*
+ * working state for accumArrayResultArr() and friends
+ * note that the input must be arrays, and the same array type is returned
+ */
+typedef struct ArrayBuildStateArr
+{
+	MemoryContext mcontext;		/* where all the temp stuff is kept */
+	char	   *data;			/* accumulated data */
+	bits8	   *nullbitmap;		/* bitmap of is-null flags, or NULL if none */
+	int			abytes;			/* allocated length of "data" */
+	int			nbytes;			/* number of bytes used so far */
+	int			aitems;			/* allocated length of bitmap (in elements) */
+	int			nitems;			/* total number of elements in result */
+	int			ndims;			/* current dimensions of result */
+	int			dims[MAXDIM];
+	int			lbs[MAXDIM];
+	Oid			array_type;		/* data type of the arrays */
+	Oid			element_type;	/* data type of the array elements */
+} ArrayBuildStateArr;
+
+/*
+ * working state for accumArrayResultAny() and friends
+ * these functions handle both cases
+ */
+typedef struct ArrayBuildStateAny
+{
+	/* Exactly one of these is not NULL: */
+	ArrayBuildState *scalarstate;
+	ArrayBuildStateArr *arraystate;
+} ArrayBuildStateAny;
+
 /*
  * structure to cache type metadata needed for array manipulation
  */
@@ -252,6 +284,9 @@ extern void deconstruct_array(ArrayType *array,
 				  int elmlen, bool elmbyval, char elmalign,
 				  Datum **elemsp, bool **nullsp, int *nelemsp);
 extern bool array_contains_nulls(ArrayType *array);
+
+extern ArrayBuildState *initArrayResult(Oid element_type,
+				MemoryContext rcontext);
 extern ArrayBuildState *accumArrayResult(ArrayBuildState *astate,
 				 Datum dvalue, bool disnull,
 				 Oid element_type,
@@ -261,6 +296,24 @@ extern Datum makeArrayResult(ArrayBuildState *astate,
 extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims,
 				  int *dims, int *lbs, MemoryContext rcontext, bool release);
 
+extern ArrayBuildStateArr *initArrayResultArr(Oid array_type, Oid element_type,
+				   MemoryContext rcontext);
+extern ArrayBuildStateArr *accumArrayResultArr(ArrayBuildStateArr *astate,
+					Datum dvalue, bool disnull,
+					Oid array_type,
+					MemoryContext rcontext);
+extern Datum makeArrayResultArr(ArrayBuildStateArr *astate,
+				   MemoryContext rcontext, bool release);
+
+extern ArrayBuildStateAny *initArrayResultAny(Oid input_type,
+				   MemoryContext rcontext);
+extern ArrayBuildStateAny *accumArrayResultAny(ArrayBuildStateAny *astate,
+					Datum dvalue, bool disnull,
+					Oid input_type,
+					MemoryContext rcontext);
+extern Datum makeArrayResultAny(ArrayBuildStateAny *astate,
+				   MemoryContext rcontext, bool release);
+
 extern ArrayIterator array_create_iterator(ArrayType *arr, int slice_ndim);
 extern bool array_iterate(ArrayIterator iterator, Datum *value, bool *isnull);
 extern void array_free_iterator(ArrayIterator iterator);
@@ -292,6 +345,8 @@ extern ArrayType *create_singleton_array(FunctionCallInfo fcinfo,
 
 extern Datum array_agg_transfn(PG_FUNCTION_ARGS);
 extern Datum array_agg_finalfn(PG_FUNCTION_ARGS);
+extern Datum array_agg_array_transfn(PG_FUNCTION_ARGS);
+extern Datum array_agg_array_finalfn(PG_FUNCTION_ARGS);
 
 /*
  * prototypes for functions defined in array_typanalyze.c
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 07d24d4cb8a0284e12cf59dd0a739f3397757c1d..1a556f8ae291dd907d8632d0c593efebe1641cc1 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -128,6 +128,7 @@ extern void get_type_category_preferred(Oid typid,
 extern Oid	get_typ_typrelid(Oid typid);
 extern Oid	get_element_type(Oid typid);
 extern Oid	get_array_type(Oid typid);
+extern Oid	get_promoted_array_type(Oid typid);
 extern Oid	get_base_element_type(Oid typid);
 extern void getTypeInputInfo(Oid type, Oid *typInput, Oid *typIOParam);
 extern void getTypeOutputInfo(Oid type, Oid *typOutput, bool *typIsVarlena);
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index 1d025d465332f72d26649489213ffa9c103831d8..56295715a18b56fdf8281fda8d6a9252ac994c63 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -268,7 +268,7 @@ static Datum plperl_sv_to_datum(SV *sv, Oid typid, int32 typmod,
 				   bool *isnull);
 static void _sv_to_datum_finfo(Oid typid, FmgrInfo *finfo, Oid *typioparam);
 static Datum plperl_array_to_datum(SV *src, Oid typid, int32 typmod);
-static ArrayBuildState *array_to_datum_internal(AV *av, ArrayBuildState *astate,
+static void array_to_datum_internal(AV *av, ArrayBuildState *astate,
 						int *ndims, int *dims, int cur_depth,
 						Oid arraytypid, Oid elemtypid, int32 typmod,
 						FmgrInfo *finfo, Oid typioparam);
@@ -1127,7 +1127,7 @@ get_perl_array_ref(SV *sv)
 /*
  * helper function for plperl_array_to_datum, recurses for multi-D arrays
  */
-static ArrayBuildState *
+static void
 array_to_datum_internal(AV *av, ArrayBuildState *astate,
 						int *ndims, int *dims, int cur_depth,
 						Oid arraytypid, Oid elemtypid, int32 typmod,
@@ -1168,10 +1168,10 @@ array_to_datum_internal(AV *av, ArrayBuildState *astate,
 						 errmsg("multidimensional arrays must have array expressions with matching dimensions")));
 
 			/* recurse to fetch elements of this sub-array */
-			astate = array_to_datum_internal(nav, astate,
-											 ndims, dims, cur_depth + 1,
-											 arraytypid, elemtypid, typmod,
-											 finfo, typioparam);
+			array_to_datum_internal(nav, astate,
+									ndims, dims, cur_depth + 1,
+									arraytypid, elemtypid, typmod,
+									finfo, typioparam);
 		}
 		else
 		{
@@ -1192,12 +1192,10 @@ array_to_datum_internal(AV *av, ArrayBuildState *astate,
 									 typioparam,
 									 &isnull);
 
-			astate = accumArrayResult(astate, dat, isnull,
-									  elemtypid, CurrentMemoryContext);
+			(void) accumArrayResult(astate, dat, isnull,
+									elemtypid, CurrentMemoryContext);
 		}
 	}
-
-	return astate;
 }
 
 /*
@@ -1222,18 +1220,21 @@ plperl_array_to_datum(SV *src, Oid typid, int32 typmod)
 				 errmsg("cannot convert Perl array to non-array type %s",
 						format_type_be(typid))));
 
+	astate = initArrayResult(elemtypid, CurrentMemoryContext);
+
 	_sv_to_datum_finfo(elemtypid, &finfo, &typioparam);
 
 	memset(dims, 0, sizeof(dims));
 	dims[0] = av_len((AV *) SvRV(src)) + 1;
 
-	astate = array_to_datum_internal((AV *) SvRV(src), NULL,
-									 &ndims, dims, 1,
-									 typid, elemtypid, typmod,
-									 &finfo, typioparam);
+	array_to_datum_internal((AV *) SvRV(src), astate,
+							&ndims, dims, 1,
+							typid, elemtypid, typmod,
+							&finfo, typioparam);
 
-	if (!astate)
-		return PointerGetDatum(construct_empty_array(elemtypid));
+	/* ensure we get zero-D array for no inputs, as per PG convention */
+	if (dims[0] <= 0)
+		ndims = 0;
 
 	for (i = 0; i < ndims; i++)
 		lbs[i] = 1;
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 46eff67b428a3ed845185f716a6014df24b7a0d4..cb606afd9cfe47b14dba1ae5011d1f806686252e 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -1497,6 +1497,7 @@ select cardinality('{{{1,9},{5,6}},{{2,3},{3,4}}}'::int[]);
            8
 (1 row)
 
+-- array_agg(anynonarray)
 select array_agg(unique1) from (select unique1 from tenk1 where unique1 < 15 order by unique1) ss;
               array_agg               
 --------------------------------------
@@ -1521,6 +1522,55 @@ select array_agg(unique1) from tenk1 where unique1 < -15;
  
 (1 row)
 
+-- array_agg(anyarray)
+select array_agg(ar)
+  from (values ('{1,2}'::int[]), ('{3,4}'::int[])) v(ar);
+   array_agg   
+---------------
+ {{1,2},{3,4}}
+(1 row)
+
+select array_agg(distinct ar order by ar desc)
+  from (select array[i / 2] from generate_series(1,10) a(i)) b(ar);
+         array_agg         
+---------------------------
+ {{5},{4},{3},{2},{1},{0}}
+(1 row)
+
+select array_agg(ar)
+  from (select array_agg(array[i, i+1, i-1])
+        from generate_series(1,2) a(i)) b(ar);
+      array_agg      
+---------------------
+ {{{1,2,0},{2,3,1}}}
+(1 row)
+
+select array_agg(array[i+1.2, i+1.3, i+1.4]) from generate_series(1,3) g(i);
+                  array_agg                  
+---------------------------------------------
+ {{2.2,2.3,2.4},{3.2,3.3,3.4},{4.2,4.3,4.4}}
+(1 row)
+
+select array_agg(array['Hello', i::text]) from generate_series(9,11) g(i);
+             array_agg             
+-----------------------------------
+ {{Hello,9},{Hello,10},{Hello,11}}
+(1 row)
+
+select array_agg(array[i, nullif(i, 3), i+1]) from generate_series(1,4) g(i);
+              array_agg               
+--------------------------------------
+ {{1,1,2},{2,2,3},{3,NULL,4},{4,4,5}}
+(1 row)
+
+-- errors
+select array_agg('{}'::int[]) from generate_series(1,2);
+ERROR:  cannot accumulate empty arrays
+select array_agg(null::int[]) from generate_series(1,2);
+ERROR:  cannot accumulate null arrays
+select array_agg(ar)
+  from (values ('{1,2}'::int[]), ('{3}'::int[])) v(ar);
+ERROR:  cannot accumulate arrays of different dimensionality
 select unnest(array[1,2,3]);
  unnest 
 --------
@@ -1660,6 +1710,19 @@ select array_replace(array['AB',NULL,'CDE'],NULL,'12');
  {AB,12,CDE}
 (1 row)
 
+-- array(select array-value ...)
+select array(select array[i,i/2] from generate_series(1,5) i);
+              array              
+---------------------------------
+ {{1,0},{2,1},{3,1},{4,2},{5,2}}
+(1 row)
+
+select array(select array['Hello', i::text] from generate_series(9,11) i);
+               array               
+-----------------------------------
+ {{Hello,9},{Hello,10},{Hello,11}}
+(1 row)
+
 -- Insert/update on a column that is array of composite
 create temp table t1 (f1 int8_tbl[]);
 insert into t1 (f1[5].q1) values(42);
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index fa8a20ad1c08630e61bba19cd277f1e4b9b8482f..733c19bed8dabba1a27636932ef3be0ae11c4244 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -427,11 +427,29 @@ select cardinality('{{1,2}}'::int[]);
 select cardinality('{{1,2},{3,4},{5,6}}'::int[]);
 select cardinality('{{{1,9},{5,6}},{{2,3},{3,4}}}'::int[]);
 
+-- array_agg(anynonarray)
 select array_agg(unique1) from (select unique1 from tenk1 where unique1 < 15 order by unique1) ss;
 select array_agg(ten) from (select ten from tenk1 where unique1 < 15 order by unique1) ss;
 select array_agg(nullif(ten, 4)) from (select ten from tenk1 where unique1 < 15 order by unique1) ss;
 select array_agg(unique1) from tenk1 where unique1 < -15;
 
+-- array_agg(anyarray)
+select array_agg(ar)
+  from (values ('{1,2}'::int[]), ('{3,4}'::int[])) v(ar);
+select array_agg(distinct ar order by ar desc)
+  from (select array[i / 2] from generate_series(1,10) a(i)) b(ar);
+select array_agg(ar)
+  from (select array_agg(array[i, i+1, i-1])
+        from generate_series(1,2) a(i)) b(ar);
+select array_agg(array[i+1.2, i+1.3, i+1.4]) from generate_series(1,3) g(i);
+select array_agg(array['Hello', i::text]) from generate_series(9,11) g(i);
+select array_agg(array[i, nullif(i, 3), i+1]) from generate_series(1,4) g(i);
+-- errors
+select array_agg('{}'::int[]) from generate_series(1,2);
+select array_agg(null::int[]) from generate_series(1,2);
+select array_agg(ar)
+  from (values ('{1,2}'::int[]), ('{3}'::int[])) v(ar);
+
 select unnest(array[1,2,3]);
 select * from unnest(array[1,2,3]);
 select unnest(array[1,2,3,4.5]::float8[]);
@@ -452,6 +470,10 @@ select array_replace(array['A','B','DD','B'],'B','CC');
 select array_replace(array[1,NULL,3],NULL,NULL);
 select array_replace(array['AB',NULL,'CDE'],NULL,'12');
 
+-- array(select array-value ...)
+select array(select array[i,i/2] from generate_series(1,5) i);
+select array(select array['Hello', i::text] from generate_series(9,11) i);
+
 -- Insert/update on a column that is array of composite
 
 create temp table t1 (f1 int8_tbl[]);