2014-10-27 9:11 GMT+07:00 Ali Akbar <the.ap...@gmail.com>: > > 2014-10-27 1:38 GMT+07:00 Pavel Stehule <pavel.steh...@gmail.com>: > >> Hi >> >> My idea is using new ArrayBuilder optimized for building multidimensional >> arrays with own State type. I think so casting to ArrayBuildState is base >> of our problems, so I don't would to do. Code in array_agg_* is simple, >> little bit more complex code is in nodeSubplan.c. Some schematic changes >> are in attachments. >> > > Thanks! The structure looks clear, and thanks for the example on > nodeSubplan.c. I will restructure the v10 of the patch to this structure. >
Patch attached. Regards, -- Ali Akbar
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 7e5bcd9..f59738a 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -12046,6 +12046,22 @@ NULL baz</literallayout>(3 rows)</entry> <row> <entry> <indexterm> + <primary>array_agg</primary> + </indexterm> + <function>array_agg(<replaceable class="parameter">anyarray</replaceable>)</function> + </entry> + <entry> + any + </entry> + <entry> + the same array type as input type + </entry> + <entry>input arrays, aggregated into higher-order multidimesional array. Rejects NULL and empty array as input.</entry> + </row> + + <row> + <entry> + <indexterm> <primary>average</primary> </indexterm> <indexterm> diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml index 2f0680f..8c182a4 100644 --- a/doc/src/sgml/syntax.sgml +++ b/doc/src/sgml/syntax.sgml @@ -2238,6 +2238,11 @@ SELECT ARRAY(SELECT oid FROM pg_proc WHERE proname LIKE 'bytea%'); array ----------------------------------------------------------------------- {2011,1954,1948,1952,1951,1244,1950,2005,1949,1953,2006,31,2412,2413} + +SELECT ARRAY(SELECT array(select i) FROM generate_series(1,5) a(i)); + array +----------------------- + {{1},{2},{3},{4},{5}} (1 row) </programlisting> The subquery must return a single column. The resulting diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c index 401bad4..eb4de3b 100644 --- a/src/backend/executor/nodeSubplan.c +++ b/src/backend/executor/nodeSubplan.c @@ -231,7 +231,10 @@ ExecScanSubPlan(SubPlanState *node, bool found = false; /* TRUE if got at least one subplan tuple */ ListCell *pvar; ListCell *l; + bool use_md_array_builder; + Oid md_array_element_type; ArrayBuildState *astate = NULL; + MdArrayBuildState *mdastate = NULL; /* * MULTIEXPR subplans, when "executed", just return NULL; but first we @@ -366,8 +369,25 @@ 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); + /* + * use a fast array multidimensional builder when input is a array + * only check on first iteration. On subsequent, use the cached values + */ + if (astate == NULL && mdastate == NULL) + { + md_array_element_type = get_element_type(subplan->firstColType); + use_md_array_builder = OidIsValid(md_array_element_type); + } + + if (use_md_array_builder) + mdastate = accumMdArray(mdastate, + disnull? NULL : + DatumGetArrayTypeP(dvalue), + disnull, md_array_element_type, + oldcontext); + else + astate = accumArrayResult(astate, dvalue, disnull, + subplan->firstColType, oldcontext); /* keep scanning subplan to collect all values */ continue; } @@ -439,6 +459,8 @@ ExecScanSubPlan(SubPlanState *node, /* We return the result in the caller's context */ if (astate != NULL) result = makeArrayResult(astate, oldcontext); + else if (mdastate != NULL) + result = makeMdArray(mdastate, oldcontext); else result = PointerGetDatum(construct_empty_array(subplan->firstColType)); } @@ -951,7 +973,10 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext) ListCell *pvar; ListCell *l; bool found = false; + bool use_md_array_builder; + Oid md_array_element_type; ArrayBuildState *astate = NULL; + MdArrayBuildState *mdastate = NULL; if (subLinkType == ANY_SUBLINK || subLinkType == ALL_SUBLINK) @@ -1018,8 +1043,25 @@ 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); + /* + * use a fast array multidimensional builder when input is a array + * only check on first iteration. On subsequent, use the cached values + */ + if (astate == NULL && mdastate == NULL) + { + md_array_element_type = get_element_type(subplan->firstColType); + use_md_array_builder = OidIsValid(md_array_element_type); + } + + if (use_md_array_builder) + mdastate = accumMdArray(mdastate, + disnull? NULL : + DatumGetArrayTypeP(dvalue), + disnull, md_array_element_type, + oldcontext); + else + astate = accumArrayResult(astate, dvalue, disnull, + subplan->firstColType, oldcontext); /* keep scanning subplan to collect all values */ continue; } @@ -1075,6 +1117,9 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext) if (astate != NULL) node->curArray = makeArrayResult(astate, econtext->ecxt_per_query_memory); + else if (mdastate != NULL) + node->curArray = makeMdArray(mdastate, + econtext->ecxt_per_query_memory); else { MemoryContextSwitchTo(econtext->ecxt_per_query_memory); diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 41e973b..0261fcb 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -108,12 +108,16 @@ exprType(const Node *expr) type = exprType((Node *) tent->expr); if (sublink->subLinkType == ARRAY_SUBLINK) { - type = get_array_type(type); - if (!OidIsValid(type)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("could not find array type for data type %s", - format_type_be(exprType((Node *) tent->expr))))); + if (!OidIsValid(get_element_type(type))) + { + /* not array, so check for its array type */ + type = get_array_type(type); + if (!OidIsValid(type)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find array type for data type %s", + format_type_be(exprType((Node *) tent->expr))))); + } } } else if (sublink->subLinkType == MULTIEXPR_SUBLINK) @@ -139,12 +143,16 @@ exprType(const Node *expr) type = subplan->firstColType; if (subplan->subLinkType == ARRAY_SUBLINK) { - type = get_array_type(type); - if (!OidIsValid(type)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("could not find array type for data type %s", - format_type_be(subplan->firstColType)))); + if (!OidIsValid(get_element_type(type))) + { + /* not array, so check for its array type */ + type = get_array_type(type); + if (!OidIsValid(type)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find array type for data type %s", + format_type_be(subplan->firstColType)))); + } } } else if (subplan->subLinkType == MULTIEXPR_SUBLINK) diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 3e7dc85..8fc8b49 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -668,10 +668,16 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot, Assert(!te->resjunk); Assert(testexpr == NULL); - arraytype = get_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))); + + arraytype = exprType((Node *) te->expr); + if (!OidIsValid(get_element_type(arraytype))) + { + /* not array, so get the array type */ + arraytype = get_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))); + } prm = generate_new_param(root, arraytype, exprTypmod((Node *) te->expr), diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c index 831466d..dcb624c 100644 --- a/src/backend/utils/adt/array_userfuncs.c +++ b/src/backend/utils/adt/array_userfuncs.c @@ -16,6 +16,8 @@ #include "utils/builtins.h" #include "utils/lsyscache.h" +static Datum array_agg_transfn_internal(PG_FUNCTION_ARGS, + const char* fname); /*----------------------------------------------------------------------------- * array_push : @@ -513,8 +515,8 @@ array_agg_finalfn(PG_FUNCTION_ARGS) { Datum result; ArrayBuildState *state; - int dims[1]; - int lbs[1]; + int dims[0]; + int lbs[0]; /* * Test for null before Asserting we are in right context. This is to @@ -529,8 +531,76 @@ array_agg_finalfn(PG_FUNCTION_ARGS) state = (ArrayBuildState *) PG_GETARG_POINTER(0); + /* + * Make the result. We cannot release the ArrayBuildState 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. + */ dims[0] = state->nelems; lbs[0] = 1; + result = makeMdArrayResult(state, 1, dims, lbs, CurrentMemoryContext, false); + + PG_RETURN_DATUM(result); +} + +/* + * ARRAY_AGG(anyarray) aggregate function + */ +Datum +array_agg_anyarray_transfn(PG_FUNCTION_ARGS) +{ + Oid arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid element_type = get_element_type(arg1_typeid); + MemoryContext aggcontext; + MdArrayBuildState *state; + Datum elem; + + if (arg1_typeid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine input data type"))); + + if (!AggCheckCallContext(fcinfo, &aggcontext)) + { + /* cannot be called directly because of internal-type argument */ + elog(ERROR, "array_agg_anyarray_transfn called in non-aggregate context"); + } + + state = PG_ARGISNULL(0) ? NULL : (MdArrayBuildState *) PG_GETARG_POINTER(0); + elem = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_ARRAYTYPE_P(1); + state = accumMdArray(state, + elem, + PG_ARGISNULL(1), + element_type, + 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 ArrayBuildState pointer through nodeAgg.c's machinations. + */ + PG_RETURN_POINTER(state); +} + +Datum +array_agg_anyarray_finalfn(PG_FUNCTION_ARGS) +{ + Datum result; + MdArrayBuildState *state; + + /* + * 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 = (MdArrayBuildState *) PG_GETARG_POINTER(0); /* * Make the result. We cannot release the ArrayBuildState because @@ -538,9 +608,7 @@ array_agg_finalfn(PG_FUNCTION_ARGS) * nodeAgg.c's responsibility to reset the aggcontext when it's safe to do * so. */ - result = makeMdArrayResult(state, 1, dims, lbs, - CurrentMemoryContext, - false); + result = makeMdCustomArray(state, -1, NULL, NULL, CurrentMemoryContext, false); PG_RETURN_DATUM(result); } diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 6c8b41d..1c922d5 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4576,6 +4576,9 @@ array_insert_slice(ArrayType *destArray, /* * accumArrayResult - accumulate one (more) Datum for an array result * + * Beware: use this only for scalar datums. For Array datums, use + * accumMdArray and friends. + * * astate is working state (NULL on first call) * rcontext is where to keep working state */ @@ -4588,6 +4591,9 @@ accumArrayResult(ArrayBuildState *astate, MemoryContext arr_context, oldcontext; + /* element_type must not be an array! */ + Assert(!OidIsValid(get_element_type(element_type))); + if (astate == NULL) { /* First time through --- initialize */ @@ -4713,6 +4719,269 @@ makeMdArrayResult(ArrayBuildState *astate, return PointerGetDatum(result); } +/* + * accumMdArray - accumulate one (more) array Datum for accumulation + * + * astate is working state (NULL on first call) + * element_type is the type that array hold + * rcontext is where to keep working state + */ +MdArrayBuildState * +accumMdArray(MdArrayBuildState *astate, + ArrayType *arg, bool disnull, + Oid element_type, + MemoryContext rcontext) +{ + return accumMdCustomArray(astate, arg, disnull, element_type, + true, rcontext); +} + +/* + * accumMdCustomArray - accumMdArray with option to not check + * dims and lbs + * + * astate is working state (NULL on first call) + * element_type is the type that array hold + * check_dims_and_lbs is the option to check dims and lbs + * rcontext is where to keep working state + */ +MdArrayBuildState * +accumMdCustomArray(MdArrayBuildState *astate, + ArrayType *arg, bool disnull, + Oid element_type, + bool check_dims_and_lbs, + MemoryContext rcontext) +{ + MemoryContext arr_context, + oldcontext; + + int *dims, + *lbs, + ndims, + nitems, + ndatabytes; + char *data; + int i; + + /* element_type must not be an array! */ + Assert(!OidIsValid(get_element_type(element_type))); + + if (disnull) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot accumulate null arrays"))); + + 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 (ndims == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + 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))); + + if (astate == NULL) + { + /* 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); + + /* array accumulate */ + astate = (MdArrayBuildState *) palloc(sizeof(MdArrayBuildState)); + astate->mcontext = arr_context; + + /* + * the resulting dimensions is n + 1, with first dimension is the number of + * array accumulated + */ + astate->ndims = ndims + 1; + astate->dims = (int *) palloc((astate->ndims) * sizeof(int)); + astate->lbs = (int *) palloc((astate->ndims) * sizeof(int)); + astate->dims[0] = 0; + memcpy(&astate->dims[1], dims, ndims * sizeof(int)); + astate->lbs[0] = 1; + memcpy(&astate->lbs[1], lbs, ndims * sizeof(int)); + + astate->aitems = 0; + astate->nbytes = 0; + astate->hasnull = false; + astate->nullbitmap = NULL; + astate->nitems = 0; + + astate->abytes = ndatabytes >= 512 ? 4 * ndatabytes : 1024; + astate->data = (char *) palloc(astate->abytes); + + astate->element_type = element_type; + get_typlenbyvalalign(element_type, + &astate->typlen, + &astate->typbyval, + &astate->typalign); + } + else + { + oldcontext = MemoryContextSwitchTo(astate->mcontext); + + Assert(astate->element_type == element_type); + + if (check_dims_and_lbs) + { + if (astate->ndims != ndims + 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("cannot accumulate incompatible arrays"), + errdetail("Arrays of %d and %d dimensions are not " + "compatible for accumulation.", + astate->ndims - 1, ndims))); + + 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 incompatible arrays"), + errdetail("Arrays with differing element dimensions are " + "not compatible for concatenation."))); + } + + /* grow array if needed */ + if (astate->nbytes + ndatabytes >= astate->abytes) + { + astate->abytes *= 2; + astate->data = (char *) + repalloc(astate->data, astate->abytes); + } + if (astate->hasnull && astate->nitems + nitems >= astate->aitems) + { + astate->aitems *= 2; + astate->nullbitmap = (bits8 *) + repalloc(astate->nullbitmap, (astate->aitems + 7) / 8); + } + } + + memcpy(astate->data + astate->nbytes, data, ndatabytes); + astate->nbytes += ndatabytes; + + if (astate->hasnull || ARR_HASNULL(arg)) + { + if (!astate->hasnull) + { + /* arbitrary number if nitems below 32 */ + astate->aitems = astate->nitems < 32 ? 64 : astate->nitems * 8; + astate->hasnull = true; + astate->nullbitmap = (bits8 *) palloc((astate->aitems + 7) / 8); + array_bitmap_copy(astate->nullbitmap, 0, + NULL, 0, + astate->nitems); + } + array_bitmap_copy(astate->nullbitmap, astate->nitems, + ARR_NULLBITMAP(arg), 0, + nitems); + astate->nitems += nitems; + } + astate->dims[0] += 1; + + MemoryContextSwitchTo(oldcontext); + + return astate; +} + +/* + * makeMdArray - produce N+1-D final result of accumMdArray + * + * astate is working state (not NULL) + * rcontext is where to construct result + */ +Datum +makeMdArray(MdArrayBuildState *astate, + MemoryContext rcontext) +{ + return makeMdCustomArray(astate, -1, NULL, NULL, rcontext, true); +} + +/* + * makeMdCustomArray - produce custom-D final result of accumMdArray + * + * beware: no check that specified dimensions match the number of values + * accumulated. + * + * astate is working state (not NULL) + * rcontext is where to construct result + * release is true if okay to release working state + * ndims = -1 will produce N+1-D array, with dims and lbs value ignored + */ +Datum +makeMdCustomArray(MdArrayBuildState *astate, + int ndims, + int *dims, + int *lbs, + MemoryContext rcontext, + bool release) +{ + ArrayType *result; + MemoryContext oldcontext; + + int dataoffset, + nbytes; + + /* Build the final array result in rcontext */ + oldcontext = MemoryContextSwitchTo(rcontext); + + if (ndims == -1) + { + ndims = astate->ndims; + dims = astate->dims; + lbs = astate->lbs; + } + + nbytes = astate->nbytes; + /* compute required space */ + if (astate->hasnull) + { + dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, astate->nitems); + nbytes += dataoffset; + } + else + { + dataoffset = 0; + nbytes += ARR_OVERHEAD_NONULLS(ndims); + } + + result = (ArrayType *) palloc0(nbytes); + SET_VARSIZE(result, nbytes); + result->ndim = ndims; + result->dataoffset = dataoffset; + result->elemtype = astate->element_type; + + memcpy(ARR_DIMS(result), dims, ndims * sizeof(int)); + memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int)); + memcpy(ARR_DATA_PTR(result), astate->data, astate->nbytes); + + if (astate->hasnull) + 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); +} + Datum array_larger(PG_FUNCTION_ARGS) { diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h index 3ba9e5e..2005199 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 ( 6005 n 0 array_agg_anyarray_transfn array_agg_anyarray_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 b6dc1b8..9273c1f 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -879,11 +879,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 = 6003 ( array_agg_anyarray_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_anyarray_transfn _null_ _null_ _null_ )); +DESCR("aggregate transition function"); +DATA(insert OID = 6004 ( array_agg_anyarray_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_anyarray_finalfn _null_ _null_ _null_ )); +DESCR("aggregate final function"); +DATA(insert OID = 6005 ( 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 e744314..b8f0fe1 100644 --- a/src/include/utils/array.h +++ b/src/include/utils/array.h @@ -75,7 +75,8 @@ typedef struct } ArrayType; /* - * working state for accumArrayResult() and friends + * working state for array accumulation of scalar datums + * with accumArrayResult() and friends */ typedef struct ArrayBuildState { @@ -91,6 +92,34 @@ typedef struct ArrayBuildState } ArrayBuildState; /* + * array build state for array accumulation of array datums + * with accumMdArray() and friends + */ +typedef struct +{ + MemoryContext mcontext; /* where all the temp stuff is kept */ + + char *data; /* array of accumulated data */ + bits8 *nullbitmap; /* bitmap of is-null flags for data */ + + int abytes; /* allocated length of above arrays */ + int aitems; /* allocated length of above arrays */ + int nbytes; /* number of used bytes in above arrays */ + int nitems; /* number of elements in above arrays */ + + int ndims; /* element dimensions */ + int *dims; + int *lbs; + + bool hasnull; /* any element has null */ + + Oid element_type; /* data type of the Datums */ + int16 typlen; /* needed info about datatype */ + bool typbyval; + char typalign; +} MdArrayBuildState; + +/* * structure to cache type metadata needed for array manipulation */ typedef struct ArrayMetaState @@ -260,6 +289,19 @@ extern Datum makeArrayResult(ArrayBuildState *astate, MemoryContext rcontext); extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims, int *dims, int *lbs, MemoryContext rcontext, bool release); +extern MdArrayBuildState *accumMdArray(MdArrayBuildState *astate, + ArrayType *arg, bool disnull, + Oid element_type, + MemoryContext rcontext); +extern MdArrayBuildState *accumMdCustomArray(MdArrayBuildState *astate, + ArrayType *arg, bool disnull, + Oid element_type, + bool check_dims_and_lbs, + MemoryContext rcontext); +extern Datum makeMdArray(MdArrayBuildState *astate, + MemoryContext rcontext); +extern Datum makeMdCustomArray(MdArrayBuildState *astate, int ndims, + int *dims, int *lbs, MemoryContext rcontext, bool release); extern ArrayIterator array_create_iterator(ArrayType *arr, int slice_ndim); extern bool array_iterate(ArrayIterator iterator, Datum *value, bool *isnull); @@ -292,6 +334,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_anyarray_transfn(PG_FUNCTION_ARGS); +extern Datum array_agg_anyarray_finalfn(PG_FUNCTION_ARGS); /* * prototypes for functions defined in array_typanalyze.c diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out index 58df854..607aeea 100644 --- a/src/test/regress/expected/aggregates.out +++ b/src/test/regress/expected/aggregates.out @@ -914,6 +914,76 @@ select array_agg(distinct a order by a desc nulls last) {3,2,1,NULL} (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) + +-- array_agg(anyarray), varlena types +select array_agg(array[1.2,1.3,1.4]) from generate_series(1,3); + array_agg +--------------------------------------------- + {{1.2,1.3,1.4},{1.2,1.3,1.4},{1.2,1.3,1.4}} +(1 row) + +select array_agg(array['Hello','Hohoho','Hi']) from generate_series(1,3); + array_agg +--------------------------------------------------------- + {{Hello,Hohoho,Hi},{Hello,Hohoho,Hi},{Hello,Hohoho,Hi}} +(1 row) + +-- array_agg(anyarray), arrays with nulls +select array_agg(array[i, null, i+1, null, i+2]) from generate_series(1,3) g(i); + array_agg +--------------------------------------------------------- + {{1,NULL,2,NULL,3},{2,NULL,3,NULL,4},{3,NULL,4,NULL,5}} +(1 row) + +select array_agg(array[1.1+ i, null, 1.1+i+1, null, 1.1+i+2]) from generate_series(1,3) g(i); + array_agg +--------------------------------------------------------------------------- + {{2.1,NULL,3.1,NULL,4.1},{3.1,NULL,4.1,NULL,5.1},{4.1,NULL,5.1,NULL,6.1}} +(1 row) + +select array_agg(array[null, 'Hello','Hohoho', null,'Hi']) from generate_series(1,3); + array_agg +--------------------------------------------------------------------------------------- + {{NULL,Hello,Hohoho,NULL,Hi},{NULL,Hello,Hohoho,NULL,Hi},{NULL,Hello,Hohoho,NULL,Hi}} +(1 row) + +select array_agg(array[[null, 'Hello', null, 'Hi'],['Hello', null, 'Hi', null]]) from generate_series(1,2); + array_agg +------------------------------------------------------------------------------------------- + {{{NULL,Hello,NULL,Hi},{Hello,NULL,Hi,NULL}},{{NULL,Hello,NULL,Hi},{Hello,NULL,Hi,NULL}}} +(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 incompatible arrays +DETAIL: Arrays with differing element dimensions are not compatible for concatenation. -- multi-arg aggs, strict/nonstrict, distinct/order by select aggfstr(a,b,c) from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c); diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 46eff67..e80ebec 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -1521,6 +1521,23 @@ select array_agg(unique1) from tenk1 where unique1 < -15; (1 row) +select array(select unique1 from tenk1 where unique1 < 15 order by unique1); + array +-------------------------------------- + {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14} +(1 row) + +select array(select array[i,i/2] from generate_series(1,5) a(i)); + array +--------------------------------- + {{1,0},{2,1},{3,1},{4,2},{5,2}} +(1 row) + +-- cannot accumulate null arrays and empty arrays +select array(select null::int[]); +ERROR: cannot accumulate null arrays +select array(select '{}'::int[]); +ERROR: cannot accumulate empty arrays select unnest(array[1,2,3]); unnest -------- diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql index 8096a6f..a70419f 100644 --- a/src/test/regress/sql/aggregates.sql +++ b/src/test/regress/sql/aggregates.sql @@ -322,6 +322,32 @@ select array_agg(distinct a order by a desc) select array_agg(distinct a order by a desc nulls last) from (values (1),(2),(1),(3),(null),(2)) v(a); +-- 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); + +-- array_agg(anyarray), varlena types +select array_agg(array[1.2,1.3,1.4]) from generate_series(1,3); +select array_agg(array['Hello','Hohoho','Hi']) from generate_series(1,3); + +-- array_agg(anyarray), arrays with nulls +select array_agg(array[i, null, i+1, null, i+2]) from generate_series(1,3) g(i); +select array_agg(array[1.1+ i, null, 1.1+i+1, null, 1.1+i+2]) from generate_series(1,3) g(i); +select array_agg(array[null, 'Hello','Hohoho', null,'Hi']) from generate_series(1,3); + +select array_agg(array[[null, 'Hello', null, 'Hi'],['Hello', null, 'Hi', null]]) from generate_series(1,2); + +-- 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); + -- multi-arg aggs, strict/nonstrict, distinct/order by select aggfstr(a,b,c) diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index fa8a20a..cb00f5f 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -432,6 +432,12 @@ select array_agg(ten) from (select ten from tenk1 where unique1 < 15 order by un 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; +select array(select unique1 from tenk1 where unique1 < 15 order by unique1); +select array(select array[i,i/2] from generate_series(1,5) a(i)); +-- cannot accumulate null arrays and empty arrays +select array(select null::int[]); +select array(select '{}'::int[]); + select unnest(array[1,2,3]); select * from unnest(array[1,2,3]); select unnest(array[1,2,3,4.5]::float8[]);
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers