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(3 rows) array_agg(expression) - any + any non-array type array of the argument type @@ -12043,6 +12043,21 @@ NULL baz(3 rows) input values, including nulls, concatenated into an array + + + array_agg(expression) + + + any array type + + + same as argument data type + + input arrays concatenated into array of one higher dimension + (inputs must all have same dimensionality, + and cannot be empty or NULL) + + 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) - 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. 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 array_agg is equivalent to -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 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 anyelement argument allows the declaration + The extra anynonarray argument allows the declaration of array_agg_finalfn to be valid. 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[]);