From bac27394a1c69c20ec904729c593e59485c75c69 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Tue, 25 Nov 2014 12:21:22 -0500
Subject: [PATCH] Support arrays as input to array_agg() and ARRAY(SELECT ...).

These cases formerly failed with errors about "could not find array type
for data type".  Now they yield arrays of the same element type and one
higher dimension.

The implementation involves creating functions with API similar to the
existing accumArrayResult() family.  I (tgl) also extended the base family
by adding an initArrayResult() function, which allows callers to avoid
special-casing the zero-inputs case if they just want an empty array as
result.  (Not all do, so the previous calling convention remains valid.)
This allowed simplifying some existing code in xml.c and plperl.c.

Ali Akbar, reviewed by Pavel Stehule, significantly modified by me
---
 doc/src/sgml/func.sgml                  |  17 +-
 doc/src/sgml/syntax.sgml                |  13 +-
 doc/src/sgml/xaggr.sgml                 |   8 +-
 src/backend/executor/nodeSubplan.c      |  38 +-
 src/backend/nodes/nodeFuncs.c           |   4 +-
 src/backend/optimizer/plan/subselect.c  |   2 +-
 src/backend/utils/adt/array_userfuncs.c |  88 ++++-
 src/backend/utils/adt/arrayfuncs.c      | 495 ++++++++++++++++++++++--
 src/backend/utils/adt/xml.c             |  31 +-
 src/backend/utils/cache/lsyscache.c     |  21 +
 src/include/catalog/catversion.h        |   2 +-
 src/include/catalog/pg_aggregate.h      |   1 +
 src/include/catalog/pg_proc.h           |  18 +-
 src/include/utils/array.h               |  55 +++
 src/include/utils/lsyscache.h           |   1 +
 src/pl/plperl/plperl.c                  |  33 +-
 src/test/regress/expected/arrays.out    |  63 +++
 src/test/regress/sql/arrays.sql         |  22 ++
 18 files changed, 797 insertions(+), 115 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 90a3460a712..baf81ee0404 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 399ae070759..6f8b7e8b28e 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 cc8ec64f235..ef7cff48794 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 401bad45b59..d9faf2000cd 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 41e973b1236..ae857a0cbe9 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 42b6d0a2cb9..579d021893c 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 831466dec91..50ea4d226b7 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 6c8b41d2a91..743351b95e0 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 119dfc7efec..7d903120e5b 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 552e498cf57..73138e0a5bb 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 bdaf4176f6a..b670902b7d2 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 3ba9e5e1b26..32793537616 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 5d4e889af45..56399ac5565 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 e744314c51b..e1d57490168 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 07d24d4cb8a..1a556f8ae29 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 1d025d46533..56295715a18 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 46eff67b428..cb606afd9cf 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 fa8a20ad1c0..733c19bed8d 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[]);
-- 
GitLab