* 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