* 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

Reply via email to