2014-10-25 10:29 GMT+07:00 Ali Akbar <the.ap...@gmail.com>: > I fixed small issue in regress tests and I enhanced tests for varlena >> types and null values. > > Thanks. > > it is about 15% faster than original implementation. > > 15% faster than array_agg(scalar)? I haven't verify the performance, but > because the internal array data and null bitmap is copied as-is, that will > be faster. > > 2014-10-25 1:51 GMT+07:00 Pavel Stehule <pavel.steh...@gmail.com>: > >> Hi Ali >> > >> I checked a code. I am thinking so code organization is not good. >> accumArrayResult is too long now. makeMdArrayResult will not work, when >> arrays was joined (it is not consistent now). I don't like a usage of >> state->is_array_accum in array_userfunc.c -- it is signal of wrong >> wrapping. >> > Yes, i was thinking the same. Attached WIP patch to reorganizate the code. > makeMdArrayResult works now, with supplied arguments act as override from > default values calculated from ArrayBuildStateArray. > > In array_userfunc.c, because we don't want to override ndims, dims and > lbs, i copied array_agg_finalfn and only change the call to > makeMdArrayResult (we don't uses makeArrayResult because we want to set > release to false). Another alternative is to create new > makeArrayResult-like function that has parameter bool release. >
adding makeArrayResult1 (do you have better name for this?), that accepts bool release parameter. array_agg_finalfn becomes more clean, and no duplicate code in array_agg_anyarray_finalfn. > next question: there is function array_append(anyarray, anyelement). Isn't >> time to define array_append(anyarray, anyarray) now? >> > > There is array_cat(anyarray, anyarray): > > /*----------------------------------------------------------------------------- > * array_cat : > * concatenate two nD arrays to form an nD array, or > * push an (n-1)D array onto the end of an nD array > > *---------------------------------------------------------------------------- > */ > 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/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..b55f3f0 100644 --- a/src/backend/utils/adt/array_userfuncs.c +++ b/src/backend/utils/adt/array_userfuncs.c @@ -16,7 +16,6 @@ #include "utils/builtins.h" #include "utils/lsyscache.h" - /*----------------------------------------------------------------------------- * array_push : * push an element onto either end of a one-dimensional array @@ -513,8 +512,6 @@ array_agg_finalfn(PG_FUNCTION_ARGS) { Datum result; ArrayBuildState *state; - int dims[1]; - int lbs[1]; /* * Test for null before Asserting we are in right context. This is to @@ -529,18 +526,28 @@ array_agg_finalfn(PG_FUNCTION_ARGS) state = (ArrayBuildState *) PG_GETARG_POINTER(0); - dims[0] = state->nelems; - lbs[0] = 1; - /* * 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. */ - result = makeMdArrayResult(state, 1, dims, lbs, - CurrentMemoryContext, - false); + result = makeArrayResult1(state, CurrentMemoryContext, false); PG_RETURN_DATUM(result); } + +/* + * ARRAY_AGG(anyarray) aggregate function + */ +Datum +array_agg_anyarray_transfn(PG_FUNCTION_ARGS) +{ + return array_agg_transfn(fcinfo); +} + +Datum +array_agg_anyarray_finalfn(PG_FUNCTION_ARGS) +{ + return array_agg_finalfn(fcinfo); +} diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 6c8b41d..df2a2d6 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -145,6 +145,19 @@ static int width_bucket_array_variable(Datum operand, Oid collation, TypeCacheEntry *typentry); +/* internal functions of accumArrayResult */ +static ArrayBuildStateScalar *initializeArrayBuildStateScalar(Oid element_type, + MemoryContext arr_context); +static ArrayBuildStateArray *initializeArrayBuildStateArray(Oid subelement_type, + MemoryContext arr_context); +static void appendScalarDatum(ArrayBuildStateScalar *astate_scalar, + Datum dvalue, bool disnull); +static void appendArrayDatum(ArrayBuildStateArray *astate_array, + Datum dvalue, bool disnull); + +/* internal function of makeMdArrayResult */ +static ArrayType *makeMdArrayResultArray(ArrayBuildStateArray *astate, + int ndims, int *dims, int *lbs); /* * array_in : @@ -4588,9 +4601,13 @@ accumArrayResult(ArrayBuildState *astate, MemoryContext arr_context, oldcontext; + ArrayBuildStateScalar *astate_scalar = NULL; /* for scalar datum accumulation */ + ArrayBuildStateArray *astate_array = NULL; /* for array datum accumulation */ + if (astate == NULL) { /* First time through --- initialize */ + Oid subelement_type = get_element_type(element_type); /* Make a temporary context to hold all the junk */ arr_context = AllocSetContextCreate(rcontext, @@ -4599,33 +4616,127 @@ accumArrayResult(ArrayBuildState *astate, 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); + + if (!OidIsValid(subelement_type)) + { + astate_scalar = initializeArrayBuildStateScalar(element_type, arr_context); + astate = (ArrayBuildState *) astate_scalar; + + appendScalarDatum(astate_scalar, dvalue, disnull); + } + else + { + astate_array = initializeArrayBuildStateArray(subelement_type, arr_context); + astate = (ArrayBuildState *) astate_array; + + appendArrayDatum(astate_array, dvalue, disnull); + } } else { oldcontext = MemoryContextSwitchTo(astate->mcontext); - Assert(astate->element_type == element_type); - /* enlarge dvalues[]/dnulls[] if needed */ - if (astate->nelems >= astate->alen) + if (!astate->is_array_accum) { - astate->alen *= 2; - astate->dvalues = (Datum *) - repalloc(astate->dvalues, astate->alen * sizeof(Datum)); - astate->dnulls = (bool *) - repalloc(astate->dnulls, astate->alen * sizeof(bool)); + Assert(astate->element_type == element_type); + astate_scalar = (ArrayBuildStateScalar *) astate; + + /* enlarge dvalues[]/dnulls[] if needed */ + if (astate_scalar->nelems >= astate_scalar->alen) + { + astate_scalar->alen *= 2; + astate_scalar->dvalues = (Datum *) + repalloc(astate_scalar->dvalues, astate_scalar->alen * sizeof(Datum)); + astate_scalar->dnulls = (bool *) + repalloc(astate_scalar->dnulls, astate_scalar->alen * sizeof(bool)); + } + + appendScalarDatum(astate_scalar, dvalue, disnull); + } + else + { + Assert(astate->element_type == get_element_type(element_type)); + astate_array = (ArrayBuildStateArray *) astate; + /* + * alloc & realloc placed in appendArrayDatum because + * we need to know the dvalue length first + */ + appendArrayDatum(astate_array, dvalue, disnull); } } + MemoryContextSwitchTo(oldcontext); + + return astate; +} + +/* + * Initialize ArrayBuildState for scalar accumulation (internal function of + * accumArrayResult) + */ +ArrayBuildStateScalar * +initializeArrayBuildStateScalar(Oid element_type, + MemoryContext arr_context) +{ + ArrayBuildStateScalar *astate_scalar; + + /* scalar accumulate */ + astate_scalar = (ArrayBuildStateScalar *) palloc(sizeof(ArrayBuildStateScalar)); + astate_scalar->astate.is_array_accum = false; + astate_scalar->astate.mcontext = arr_context; + + astate_scalar->alen = 64; /* arbitrary starting array size */ + astate_scalar->dvalues = (Datum *) palloc(astate_scalar->alen * sizeof(Datum)); + astate_scalar->dnulls = (bool *) palloc(astate_scalar->alen * sizeof(bool)); + astate_scalar->nelems = 0; + + astate_scalar->astate.element_type = element_type; + get_typlenbyvalalign(element_type, + &astate_scalar->astate.typlen, + &astate_scalar->astate.typbyval, + &astate_scalar->astate.typalign); + + return astate_scalar; +} + +/* + * Initialize ArrayBuildState for array accum (internal of accumArrayResult) + */ +ArrayBuildStateArray * +initializeArrayBuildStateArray(Oid subelement_type, + MemoryContext arr_context) +{ + ArrayBuildStateArray *astate_array; + + /* array accumulate */ + astate_array = (ArrayBuildStateArray *) palloc(sizeof(ArrayBuildStateArray)); + astate_array->astate.is_array_accum = true; + astate_array->astate.mcontext = arr_context; + + astate_array->abytes = 0; + astate_array->aitems = 0; + astate_array->data = NULL; + astate_array->nbytes = 0; + astate_array->hasnull = false; + astate_array->nullbitmap = NULL; + astate_array->nitems = 0; + astate_array->narray = 0; + + astate_array->astate.element_type = subelement_type; + get_typlenbyvalalign(subelement_type, + &astate_array->astate.typlen, + &astate_array->astate.typbyval, + &astate_array->astate.typalign); + + return astate_array; +} + +/* + * Append a datum for scalar accumulation (internal of accumArrayResult) + */ +void +appendScalarDatum(ArrayBuildStateScalar *astate_scalar, + Datum dvalue, bool disnull) +{ /* * Ensure pass-by-ref stuff is copied into mcontext; and detoast it too if * it's varlena. (You might think that detoasting is not needed here @@ -4634,25 +4745,129 @@ accumArrayResult(ArrayBuildState *astate, * because that would mean array_agg_finalfn damages its input, which is * verboten. Also, this way frequently saves one copying step.) */ - if (!disnull && !astate->typbyval) + if (!disnull && !astate_scalar->astate.typbyval) { - if (astate->typlen == -1) + if (astate_scalar->astate.typlen == -1) dvalue = PointerGetDatum(PG_DETOAST_DATUM_COPY(dvalue)); else - dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen); + dvalue = datumCopy(dvalue, astate_scalar->astate.typbyval, + astate_scalar->astate.typlen); } - astate->dvalues[astate->nelems] = dvalue; - astate->dnulls[astate->nelems] = disnull; - astate->nelems++; + astate_scalar->dvalues[astate_scalar->nelems] = dvalue; + astate_scalar->dnulls[astate_scalar->nelems] = disnull; + astate_scalar->nelems++; +} - MemoryContextSwitchTo(oldcontext); +/* + * Append a datum for array accum (internal of accumArrayResult) + */ +void +appendArrayDatum(ArrayBuildStateArray *astate_array, + Datum dvalue, bool disnull) +{ + ArrayType *arg; + int *dims, + *lbs, + ndims, + nitems, + ndatabytes; + char *data; + int i; - return astate; + if (disnull) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot accumulate null arrays"))); + + arg = DatumGetArrayTypeP(dvalue); + + 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_array->data == NULL) + { + /* first allocation */ + 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))); + + astate_array->ndims = ndims; + astate_array->dims = (int *) palloc(ndims * sizeof(int)); + astate_array->lbs = (int *) palloc(ndims * sizeof(int)); + memcpy(astate_array->dims, dims, ndims * sizeof(int)); + memcpy(astate_array->lbs, lbs, ndims * sizeof(int)); + + astate_array->abytes = ndatabytes >= 512 ? 4 * ndatabytes : 1024; + astate_array->data = (char *) palloc(astate_array->abytes); + astate_array->aitems = 4 * nitems; + } + else + { + if (astate_array->ndims != ndims) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("cannot accumulate incompatible arrays"), + errdetail("Arrays of %d and %d dimensions are not " + "compatible for concatenation.", + astate_array->ndims, ndims))); + + for (i = 0; i < ndims; i++) + if (astate_array->dims[i] != dims[i] || astate_array->lbs[i] != 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."))); + + if (astate_array->nbytes + ndatabytes >= astate_array->abytes) + { + astate_array->abytes *= 2; + astate_array->data = (char *) + repalloc(astate_array->data, astate_array->abytes); + } + if (astate_array->nitems + nitems >= astate_array->aitems) + { + astate_array->aitems *= 2; + astate_array->nullbitmap = (bits8 *) + repalloc(astate_array->nullbitmap, (astate_array->aitems + 7) / 8); + } + } + memcpy(astate_array->data + astate_array->nbytes, data, ndatabytes); + astate_array->nbytes += ndatabytes; + + if (astate_array->hasnull || ARR_HASNULL(arg)) + { + if (!astate_array->hasnull) + { + astate_array->hasnull = true; + astate_array->nullbitmap = (bits8 *) palloc((astate_array->aitems + 7) / 8); + array_bitmap_copy(astate_array->nullbitmap, 0, + NULL, 0, + astate_array->nitems); + } + array_bitmap_copy(astate_array->nullbitmap, astate_array->nitems, + ARR_NULLBITMAP(arg), 0, + nitems); + astate_array->nitems += nitems; + } + astate_array->narray += 1; } /* - * makeArrayResult - produce 1-D final result of accumArrayResult + * makeArrayResult - produce 1-D final result of scalar accumArrayResult + * - produce N+1-D final result of array accumArrayResult * * astate is working state (not NULL) * rcontext is where to construct result @@ -4661,17 +4876,36 @@ Datum makeArrayResult(ArrayBuildState *astate, MemoryContext rcontext) { - int dims[1]; - int lbs[1]; + return makeArrayResult1(astate, rcontext, true); +} - dims[0] = astate->nelems; - lbs[0] = 1; +/* + * makeArrayResult1 - same as makeArrayResult, with option not to + * release working state + * + * release is true if okay to release working state + */ +Datum +makeArrayResult1(ArrayBuildState *astate, + MemoryContext rcontext, + bool release) +{ + if (!astate->is_array_accum) + { + int dims[1]; + int lbs[1]; + + dims[0] = ((ArrayBuildStateScalar *) astate)->nelems; + lbs[0] = 1; - return makeMdArrayResult(astate, 1, dims, lbs, rcontext, true); + return makeMdArrayResult(astate, 1, dims, lbs, rcontext, release); + } + else + return makeMdArrayResult(astate, -1, NULL, NULL, rcontext, release); } /* - * makeMdArrayResult - produce multi-D final result of accumArrayResult + * makeMdArrayResult - produce multi-D final result of scalar accumArrayResult * * beware: no check that specified dimensions match the number of values * accumulated. @@ -4679,6 +4913,11 @@ makeArrayResult(ArrayBuildState *astate, * astate is working state (not NULL) * rcontext is where to construct result * release is true if okay to release working state + * + * for array accumulation: + * ndims == -1: default (calculated from astate) + * dims == NULL: default dims (calculated from astate) + * lbs == NULL: default (calculated from astate) */ Datum makeMdArrayResult(ArrayBuildState *astate, @@ -4694,8 +4933,13 @@ makeMdArrayResult(ArrayBuildState *astate, /* Build the final array result in rcontext */ oldcontext = MemoryContextSwitchTo(rcontext); - result = construct_md_array(astate->dvalues, - astate->dnulls, + if (!astate->is_array_accum) + { + ArrayBuildStateScalar *astate_scalar; + astate_scalar = (ArrayBuildStateScalar *) astate; + + result = construct_md_array(astate_scalar->dvalues, + astate_scalar->dnulls, ndims, dims, lbs, @@ -4703,6 +4947,16 @@ makeMdArrayResult(ArrayBuildState *astate, astate->typlen, astate->typbyval, astate->typalign); + } + else + { + ArrayBuildStateArray *astate_array; + astate_array = (ArrayBuildStateArray *) astate; + result = makeMdArrayResultArray(astate_array, + ndims, + dims, + lbs); + } MemoryContextSwitchTo(oldcontext); @@ -4713,6 +4967,75 @@ makeMdArrayResult(ArrayBuildState *astate, return PointerGetDatum(result); } +/* + * makeMdArrayResultArray - internal of makeArrayResult & makeMdArrayResult + * + * beware: no check that specified dimensions match the values + * accumulated. + * + * astate is working state (not NULL) + * ndims > -1 if wants to override default (calculated from astate) + * dims not NULL if wants to override default + * lbs not NULL if wants to override default + */ +ArrayType * +makeMdArrayResultArray(ArrayBuildStateArray *astate, + int ndims, + int *dims, + int *lbs) +{ + ArrayType *result; + + int dataoffset, + nbytes; + + if (ndims == -1) + ndims = astate->ndims + 1; + + 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 = astate->ndims + 1; + result->dataoffset = dataoffset; + result->elemtype = astate->astate.element_type; + + if (dims != NULL) + memcpy(ARR_DIMS(result), dims, ndims * sizeof(int)); + else + { + ARR_DIMS(result)[0] = astate->narray; + memcpy(&(ARR_DIMS(result)[1]), astate->dims, (ndims - 1) * sizeof(int)); + } + if (lbs != NULL) + memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int)); + else + { + ARR_LBOUND(result)[0] = 1; + memcpy(&(ARR_LBOUND(result)[1]), astate->lbs, (ndims - 1) * 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); + + return 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..daaae88 100644 --- a/src/include/utils/array.h +++ b/src/include/utils/array.h @@ -76,14 +76,15 @@ typedef struct /* * working state for accumArrayResult() and friends + * + * is_array_accum: whether accumulating array values. + * (if true must be casted to ArrayBuildStateArray, else + * cast to ArrayBuildStateScalar) */ typedef struct ArrayBuildState { + bool is_array_accum; MemoryContext mcontext; /* where all the temp stuff is kept */ - Datum *dvalues; /* array of accumulated Datums */ - bool *dnulls; /* array of is-null flags for Datums */ - int alen; /* allocated length of above arrays */ - int nelems; /* number of valid entries in above arrays */ Oid element_type; /* data type of the Datums */ int16 typlen; /* needed info about datatype */ bool typbyval; @@ -91,6 +92,43 @@ typedef struct ArrayBuildState } ArrayBuildState; /* + * array build state for array accumulation of scalar datums + */ +typedef struct ArrayBuildStateScalar +{ + ArrayBuildState astate; + + Datum *dvalues; /* array of accumulated Datums */ + bool *dnulls; /* array of is-null flags for Datums */ + int alen; /* allocated length of above arrays */ + int nelems; /* number of valid entries in above arrays */ +} ArrayBuildStateScalar; + + +/* + * array build state for array accumulation of array datums + */ +typedef struct +{ + ArrayBuildState astate; + + 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 narray; /* number of array accumulated */ + + int ndims; /* element dimensions */ + int *dims; + int *lbs; + + bool hasnull; /* any element has null */ +} ArrayBuildStateArray; + +/* * structure to cache type metadata needed for array manipulation */ typedef struct ArrayMetaState @@ -258,9 +296,10 @@ extern ArrayBuildState *accumArrayResult(ArrayBuildState *astate, MemoryContext rcontext); extern Datum makeArrayResult(ArrayBuildState *astate, MemoryContext rcontext); +extern Datum makeArrayResult1(ArrayBuildState *astate, + MemoryContext rcontext, bool release); extern Datum makeMdArrayResult(ArrayBuildState *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); extern void array_free_iterator(ArrayIterator iterator); @@ -293,6 +332,9 @@ 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