mirror of https://github.com/postgres/postgres
array_agg() and unnest(). We could drop it entirely in the future, but let's keep it for a release or two as a compatibility assist.REL8_5_ALPHA1_BRANCH
parent
9e0247aba5
commit
32cc9e5533
@ -1,278 +0,0 @@ |
|||||||
/*
|
|
||||||
* Integer array aggregator / enumerator |
|
||||||
* |
|
||||||
* Mark L. Woodward |
|
||||||
* DMN Digital Music Network. |
|
||||||
* www.dmn.com |
|
||||||
* |
|
||||||
* $PostgreSQL: pgsql/contrib/intagg/int_aggregate.c,v 1.27 2008/05/12 00:00:42 alvherre Exp $ |
|
||||||
* |
|
||||||
* Copyright (C) Digital Music Network |
|
||||||
* December 20, 2001 |
|
||||||
* |
|
||||||
* This file is the property of the Digital Music Network (DMN). |
|
||||||
* It is being made available to users of the PostgreSQL system |
|
||||||
* under the BSD license. |
|
||||||
*/ |
|
||||||
#include "postgres.h" |
|
||||||
|
|
||||||
#include <ctype.h> |
|
||||||
#include <sys/types.h> |
|
||||||
|
|
||||||
#include "access/xact.h" |
|
||||||
#include "catalog/indexing.h" |
|
||||||
#include "catalog/pg_proc.h" |
|
||||||
#include "catalog/pg_type.h" |
|
||||||
#include "executor/executor.h" |
|
||||||
#include "fmgr.h" |
|
||||||
#include "miscadmin.h" |
|
||||||
#include "utils/array.h" |
|
||||||
#include "utils/builtins.h" |
|
||||||
#include "utils/memutils.h" |
|
||||||
#include "utils/syscache.h" |
|
||||||
#include "utils/lsyscache.h" |
|
||||||
|
|
||||||
PG_MODULE_MAGIC; |
|
||||||
|
|
||||||
/*
|
|
||||||
* This is actually a postgres version of a one dimensional array. |
|
||||||
* We cheat a little by using the lower-bound field as an indicator |
|
||||||
* of the physically allocated size, while the dimensionality is the |
|
||||||
* count of items accumulated so far. |
|
||||||
*/ |
|
||||||
typedef struct |
|
||||||
{ |
|
||||||
ArrayType a; |
|
||||||
int items; |
|
||||||
int lower; |
|
||||||
int4 array[1]; |
|
||||||
} PGARRAY; |
|
||||||
|
|
||||||
/* This is used to keep track of our position during enumeration */ |
|
||||||
typedef struct callContext |
|
||||||
{ |
|
||||||
PGARRAY *p; |
|
||||||
int num; |
|
||||||
int flags; |
|
||||||
} CTX; |
|
||||||
|
|
||||||
#define TOASTED 1 |
|
||||||
#define START_NUM 8 /* initial size of arrays */ |
|
||||||
#define PGARRAY_SIZE(n) (sizeof(PGARRAY) + (((n)-1)*sizeof(int4))) |
|
||||||
|
|
||||||
static PGARRAY *GetPGArray(PGARRAY * p, AggState *aggstate, bool fAdd); |
|
||||||
static PGARRAY *ShrinkPGArray(PGARRAY * p); |
|
||||||
|
|
||||||
Datum int_agg_state(PG_FUNCTION_ARGS); |
|
||||||
Datum int_agg_final_array(PG_FUNCTION_ARGS); |
|
||||||
Datum int_enum(PG_FUNCTION_ARGS); |
|
||||||
|
|
||||||
PG_FUNCTION_INFO_V1(int_agg_state); |
|
||||||
PG_FUNCTION_INFO_V1(int_agg_final_array); |
|
||||||
PG_FUNCTION_INFO_V1(int_enum); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Manage the allocation state of the array |
|
||||||
* |
|
||||||
* Note that the array needs to be in a reasonably long-lived context, |
|
||||||
* ie the Agg node's aggcontext. |
|
||||||
*/ |
|
||||||
static PGARRAY * |
|
||||||
GetPGArray(PGARRAY * p, AggState *aggstate, bool fAdd) |
|
||||||
{ |
|
||||||
if (!p) |
|
||||||
{ |
|
||||||
/* New array */ |
|
||||||
int cb = PGARRAY_SIZE(START_NUM); |
|
||||||
|
|
||||||
p = (PGARRAY *) MemoryContextAlloc(aggstate->aggcontext, cb); |
|
||||||
SET_VARSIZE(p, cb); |
|
||||||
p->a.ndim = 1; |
|
||||||
p->a.dataoffset = 0; /* we don't support nulls, for now */ |
|
||||||
p->a.elemtype = INT4OID; |
|
||||||
p->items = 0; |
|
||||||
p->lower = START_NUM; |
|
||||||
} |
|
||||||
else if (fAdd) |
|
||||||
{ |
|
||||||
/* Ensure array has space for another item */ |
|
||||||
if (p->items >= p->lower) |
|
||||||
{ |
|
||||||
PGARRAY *pn; |
|
||||||
int n = p->lower * 2; |
|
||||||
int cbNew = PGARRAY_SIZE(n); |
|
||||||
|
|
||||||
pn = (PGARRAY *) MemoryContextAlloc(aggstate->aggcontext, cbNew); |
|
||||||
memcpy(pn, p, VARSIZE(p)); |
|
||||||
SET_VARSIZE(pn, cbNew); |
|
||||||
pn->lower = n; |
|
||||||
/* do not pfree(p), because nodeAgg.c will */ |
|
||||||
p = pn; |
|
||||||
} |
|
||||||
} |
|
||||||
return p; |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
* Shrinks the array to its actual size and moves it into the standard |
|
||||||
* memory allocation context |
|
||||||
*/ |
|
||||||
static PGARRAY * |
|
||||||
ShrinkPGArray(PGARRAY * p) |
|
||||||
{ |
|
||||||
PGARRAY *pnew; |
|
||||||
|
|
||||||
/* get target size */ |
|
||||||
int cb = PGARRAY_SIZE(p->items); |
|
||||||
|
|
||||||
/* use current transaction context */ |
|
||||||
pnew = palloc(cb); |
|
||||||
memcpy(pnew, p, cb); |
|
||||||
|
|
||||||
/* fix up the fields in the new array to match normal conventions */ |
|
||||||
SET_VARSIZE(pnew, cb); |
|
||||||
pnew->lower = 1; |
|
||||||
|
|
||||||
/* do not pfree(p), because nodeAgg.c will */ |
|
||||||
|
|
||||||
return pnew; |
|
||||||
} |
|
||||||
|
|
||||||
/* Called for each iteration during an aggregate function */ |
|
||||||
Datum |
|
||||||
int_agg_state(PG_FUNCTION_ARGS) |
|
||||||
{ |
|
||||||
PGARRAY *state; |
|
||||||
PGARRAY *p; |
|
||||||
|
|
||||||
/*
|
|
||||||
* As of PG 8.1 we can actually verify that we are being used as an |
|
||||||
* aggregate function, and so it is safe to scribble on our left input. |
|
||||||
*/ |
|
||||||
if (!(fcinfo->context && IsA(fcinfo->context, AggState))) |
|
||||||
elog(ERROR, "int_agg_state may only be used as an aggregate"); |
|
||||||
|
|
||||||
if (PG_ARGISNULL(0)) |
|
||||||
state = NULL; /* first time through */ |
|
||||||
else |
|
||||||
state = (PGARRAY *) PG_GETARG_POINTER(0); |
|
||||||
p = GetPGArray(state, (AggState *) fcinfo->context, true); |
|
||||||
|
|
||||||
if (!PG_ARGISNULL(1)) |
|
||||||
{ |
|
||||||
int4 value = PG_GETARG_INT32(1); |
|
||||||
|
|
||||||
if (!p) /* internal error */ |
|
||||||
elog(ERROR, "no aggregate storage"); |
|
||||||
else if (p->items >= p->lower) /* internal error */ |
|
||||||
elog(ERROR, "aggregate storage too small"); |
|
||||||
else |
|
||||||
p->array[p->items++] = value; |
|
||||||
} |
|
||||||
PG_RETURN_POINTER(p); |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
* This is the final function used for the integer aggregator. It returns all |
|
||||||
* the integers collected as a one dimensional integer array |
|
||||||
*/ |
|
||||||
Datum |
|
||||||
int_agg_final_array(PG_FUNCTION_ARGS) |
|
||||||
{ |
|
||||||
PGARRAY *state; |
|
||||||
PGARRAY *p; |
|
||||||
PGARRAY *pnew; |
|
||||||
|
|
||||||
/*
|
|
||||||
* As of PG 8.1 we can actually verify that we are being used as an |
|
||||||
* aggregate function, and so it is safe to scribble on our left input. |
|
||||||
*/ |
|
||||||
if (!(fcinfo->context && IsA(fcinfo->context, AggState))) |
|
||||||
elog(ERROR, "int_agg_final_array may only be used as an aggregate"); |
|
||||||
|
|
||||||
if (PG_ARGISNULL(0)) |
|
||||||
state = NULL; /* zero items in aggregation */ |
|
||||||
else |
|
||||||
state = (PGARRAY *) PG_GETARG_POINTER(0); |
|
||||||
p = GetPGArray(state, (AggState *) fcinfo->context, false); |
|
||||||
|
|
||||||
pnew = ShrinkPGArray(p); |
|
||||||
PG_RETURN_POINTER(pnew); |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
* This function accepts an array, and returns one item for each entry in the |
|
||||||
* array |
|
||||||
*/ |
|
||||||
Datum |
|
||||||
int_enum(PG_FUNCTION_ARGS) |
|
||||||
{ |
|
||||||
PGARRAY *p = (PGARRAY *) PG_GETARG_POINTER(0); |
|
||||||
CTX *pc; |
|
||||||
ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; |
|
||||||
|
|
||||||
if (!rsi || !IsA(rsi, ReturnSetInfo)) |
|
||||||
ereport(ERROR, |
|
||||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
|
||||||
errmsg("int_enum called in context that cannot accept a set"))); |
|
||||||
|
|
||||||
if (!p) |
|
||||||
{ |
|
||||||
elog(WARNING, "no data sent"); |
|
||||||
PG_RETURN_NULL(); |
|
||||||
} |
|
||||||
|
|
||||||
if (!fcinfo->flinfo->fn_extra) |
|
||||||
{ |
|
||||||
/* Allocate working state */ |
|
||||||
MemoryContext oldcontext; |
|
||||||
|
|
||||||
oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt); |
|
||||||
|
|
||||||
pc = (CTX *) palloc(sizeof(CTX)); |
|
||||||
|
|
||||||
/* Don't copy attribute if you don't need to */ |
|
||||||
if (VARATT_IS_EXTENDED(p)) |
|
||||||
{ |
|
||||||
/* Toasted!!! */ |
|
||||||
pc->p = (PGARRAY *) PG_DETOAST_DATUM_COPY(p); |
|
||||||
pc->flags = TOASTED; |
|
||||||
} |
|
||||||
else |
|
||||||
{ |
|
||||||
/* Untoasted */ |
|
||||||
pc->p = p; |
|
||||||
pc->flags = 0; |
|
||||||
} |
|
||||||
/* Now that we have a detoasted array, verify dimensions */ |
|
||||||
/* We'll treat a zero-D array as empty, below */ |
|
||||||
if (pc->p->a.ndim > 1) |
|
||||||
elog(ERROR, "int_enum only accepts 1-D arrays"); |
|
||||||
pc->num = 0; |
|
||||||
fcinfo->flinfo->fn_extra = (void *) pc; |
|
||||||
MemoryContextSwitchTo(oldcontext); |
|
||||||
} |
|
||||||
else |
|
||||||
/* use existing working state */ |
|
||||||
pc = (CTX *) fcinfo->flinfo->fn_extra; |
|
||||||
|
|
||||||
/* Are we done yet? */ |
|
||||||
if (pc->p->a.ndim < 1 || pc->num >= pc->p->items) |
|
||||||
{ |
|
||||||
/* We are done */ |
|
||||||
if (pc->flags & TOASTED) |
|
||||||
pfree(pc->p); |
|
||||||
pfree(pc); |
|
||||||
fcinfo->flinfo->fn_extra = NULL; |
|
||||||
rsi->isDone = ExprEndResult; |
|
||||||
} |
|
||||||
else |
|
||||||
{ |
|
||||||
/* nope, return the next value */ |
|
||||||
int val = pc->p->array[pc->num++]; |
|
||||||
|
|
||||||
rsi->isDone = ExprMultipleResult; |
|
||||||
PG_RETURN_INT32(val); |
|
||||||
} |
|
||||||
PG_RETURN_NULL(); |
|
||||||
} |
|
Loading…
Reference in new issue