* Tom Lane ([EMAIL PROTECTED]) wrote: > Stephen Frost <[EMAIL PROTECTED]> writes: > > I was hoping to do that, but since it's an aggregate the ffunc format is > > pre-defined to require accepting the 'internal state' and nothing else, > > and to return 'anyelement' or 'anyarray' one of the inputs must be an > > 'anyelement' or 'anyarray', aiui. > > Hmm ... I hadn't been thinking about what the state type would need to > be, but certainly "bytea" is a lie given what you're really doing.
Indeed. I've updated the functions quite a bit to clean things up, including: Added many more comments, removed the unnecessary 'storage*' pointer being used, created my own structure for tracking state information, created a seperate memory context (tied to the AggContext), correctly handle NULL values, and changed the ffunc to use makeArrayResult. I also tried just tried using polymorphic types for the functions and for the aggregate and it appeared to just work: create function aaccum_sfunc (anyarray, anyelement) returns anyarray language 'C' AS 'aaccum.so', 'aaccum_sfunc' ; create function aaccum_ffunc (anyarray) returns anyarray language 'C' AS '/data/sfrost/postgres/arrays/aaccum.so', 'aaccum_ffunc' ; create aggregate aaccum ( sfunc = aaccum_sfunc, basetype = anyelement, stype = anyarray, finalfunc = aaccum_ffunc ); select aaccum(generate_series) from generate_series(1,5); aaccum ------------- {1,2,3,4,5} (1 row) (test is a table with one varchar column, abc) select aaccum(abc) from test; aaccum --------- {a,b,c} (1 row) (Added a column called 'hi', set to 'a', added b,b and c,b) select hi,aaccum(abc) from test group by hi; hi | aaccum ----+--------- b | {b,c} a | {a,b,c} (2 rows) It makes some sense that it would work as an 'anyarray' is just a variable-length type internally and so long as nothing else attempts to make sense out of our 'fake array' everything should be fine. The latest version also appears to be a bit faster than the prior version. I'm going to be running a very large query shortly using this aaccum and will report back how it goes. Please let me know if there are any other improvments or changes I should make. I'd like to submit this to -patches w/ the appropriate entries to have it be included in the core distribution. Is it acceptable to reuse the 'array_accum' name even though it was used in the documentation as an example? I'm thinking yes, but wanted to check. Thanks! Stephen
#include "postgres.h" #include "fmgr.h" #include "utils/array.h" #include "utils/memutils.h" #include "nodes/execnodes.h" PG_MODULE_MAGIC; /* Structure for storing our pointers to the * ArrayBuildState for the array we are building * and the MemoryContext in which it is being * built. Note that this structure is * considered a bytea externally and therefore * must open with an int32 defining the length. */ typedef struct { int32 vl_len; ArrayBuildState *astate; MemoryContext arrctx; } aaccum_info; /* The state-transistion function for our aggregate. */ PG_FUNCTION_INFO_V1(aaccum_sfunc); Datum aaccum_sfunc(PG_FUNCTION_ARGS) { aaccum_info *ainfo; AggState *aggstate; /* Make sure we are in an aggregate. */ if (!fcinfo->context || !IsA(fcinfo->context, AggState)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Can not call aaccum_sfunc as a non-aggregate"))); aggstate = (AggState*) fcinfo->context; /* Initial call passes NULL in for our state variable. * Allocate memory and get set up. */ if (PG_ARGISNULL(0)) { /* Allocate memory to hold the pointers to the ArrayBuildState * and the MemoryContext where we are building the array. Note * that we can do this in the CurrentMemoryContext because when * we return the storage "bytea" will be copied into the AggState * context by the caller and passed back to us on the next call. */ ainfo = (aaccum_info*) palloc(sizeof(aaccum_info)); ainfo->vl_len = sizeof(aaccum_info); ainfo->astate = NULL; /* New context created which will store our array accumulation. * The parent is the AggContext for this query since it needs to * persist for the same timeframe as the state value. * The state value holds the pointers to the ArrayBuildState and this * MemoryContext through the aaccum_info structure. */ ainfo->arrctx = AllocSetContextCreate(aggstate->aggcontext, "ArrayAccumCtx", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); } else { /* Our state variable is non-null, therefore it must be an existing * ainfo structure. */ ainfo = (aaccum_info*) PG_GETARG_BYTEA_P(0); } /* Pull the element to be added and pass it along with the ArrayBuildState * and ArrayAccumCtx MemoryContext to accumArrayResult, checking if it is * NULL or not. */ ainfo->astate = accumArrayResult(ainfo->astate, PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1), PG_ARGISNULL(1), get_fn_expr_argtype(fcinfo->flinfo, 1), ainfo->arrctx); /* Caller will copy storage into the AggContext after the first call and then * should not touch it as we will always return the same pointer passed in. */ PG_RETURN_BYTEA_P(ainfo); } /* The final function for our aggregate. */ PG_FUNCTION_INFO_V1(aaccum_ffunc); Datum aaccum_ffunc(PG_FUNCTION_ARGS) { aaccum_info *ainfo; /* Check if we are passed in a NULL */ if (PG_ARGISNULL(0)) PG_RETURN_ARRAYTYPE_P(NULL); /* Make sure we are in an aggregate. */ if (!fcinfo->context || !IsA(fcinfo->context, AggState)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Can not call aaccum_sfunc as a non-aggregate"))); ainfo = (aaccum_info*) PG_GETARG_BYTEA_P(0); /* makeArrayResult will delete ainfo->arrctx for us. */ PG_RETURN_ARRAYTYPE_P(makeArrayResult(ainfo->astate, ainfo->arrctx)); }
---------------------------(end of broadcast)--------------------------- TIP 4: Have you searched our list archives? http://archives.postgresql.org