blob: ab9cdfb9a54e12b64e7a257efcf3b887c1f87339 [file] [log] [blame]
#include "postgres.h"
#include "funcapi.h"
#include "orafunc.h"
#include "utils/builtins.h"
#include "builtins.h"
#include "lib/stringinfo.h"
PG_FUNCTION_INFO_V1(orafce_listagg1_transfn);
PG_FUNCTION_INFO_V1(orafce_listagg2_transfn);
PG_FUNCTION_INFO_V1(orafce_listagg_finalfn);
PG_FUNCTION_INFO_V1(orafce_median4_transfn);
PG_FUNCTION_INFO_V1(orafce_median4_finalfn);
PG_FUNCTION_INFO_V1(orafce_median8_transfn);
PG_FUNCTION_INFO_V1(orafce_median8_finalfn);
typedef struct
{
int alen; /* allocated length */
int nextlen; /* next allocated length */
int nelems; /* number of valid entries */
union
{
float4 *float4_values;
float8 *float8_values;
} d;
} MedianState;
int orafce_float4_cmp(const void *a, const void *b);
int orafce_float8_cmp(const void *a, const void *b);
#if PG_VERSION_NUM >= 80400 && PG_VERSION_NUM < 90000
static int
AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
{
if (fcinfo->context && IsA(fcinfo->context, AggState))
{
if (aggcontext)
*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
return 1;
}
else if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
{
if (aggcontext)
*aggcontext = ((WindowAggState *) fcinfo->context)->wincontext;
return 2;
}
/* this is just to prevent "uninitialized variable" warnings */
if (aggcontext)
*aggcontext = NULL;
return 0;
}
#endif
/****************************************************************
* listagg
*
* Concates values and returns string.
*
* Syntax:
* FUNCTION listagg(string varchar, delimiter varchar = '')
* RETURNS varchar;
*
* Note: any NULL value is ignored.
*
****************************************************************/
#if PG_VERSION_NUM >= 80400
/* subroutine to initialize state */
static StringInfo
makeStringAggState(FunctionCallInfo fcinfo)
{
StringInfo state;
MemoryContext aggcontext;
MemoryContext oldcontext;
if (!AggCheckCallContext(fcinfo, &aggcontext))
{
/* cannot be called directly because of internal-type argument */
elog(ERROR, "listagg_transfn called in non-aggregate context");
}
/*
* Create state in aggregate context. It'll stay there across subsequent
* calls.
*/
oldcontext = MemoryContextSwitchTo(aggcontext);
state = makeStringInfo();
MemoryContextSwitchTo(oldcontext);
return state;
}
static void
appendStringInfoText(StringInfo str, const text *t)
{
appendBinaryStringInfo(str, VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t));
}
#endif
Datum
orafce_listagg1_transfn(PG_FUNCTION_ARGS)
{
#if PG_VERSION_NUM >= 80400
StringInfo state;
state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
/* Append the element unless null. */
if (!PG_ARGISNULL(1))
{
if (state == NULL)
state = makeStringAggState(fcinfo);
appendStringInfoText(state, PG_GETARG_TEXT_PP(1)); /* value */
}
/*
* The transition type for string_agg() is declared to be "internal",
* which is a pass-by-value type the same size as a pointer.
*/
PG_RETURN_POINTER(state);
#else
if (!PG_ARGISNULL(1))
{
if (PG_ARGISNULL(0))
{
/*
* Just return the input. No need to copy, our caller is responsible
* for this.
*/
PG_RETURN_DATUM(PG_GETARG_DATUM(1));
}
else
return DirectFunctionCall2(textcat, PG_GETARG_DATUM(0), PG_GETARG_DATUM(1));
}
if (PG_ARGISNULL(0))
PG_RETURN_NULL();
else
PG_RETURN_DATUM(PG_GETARG_DATUM(0));
#endif
}
Datum
orafce_listagg2_transfn(PG_FUNCTION_ARGS)
{
#if PG_VERSION_NUM >= 90000
return string_agg_transfn(fcinfo);
#elif PG_VERSION_NUM >= 80400
StringInfo state;
state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
/* Append the value unless null. */
if (!PG_ARGISNULL(1))
{
/* On the first time through, we ignore the delimiter. */
if (state == NULL)
state = makeStringAggState(fcinfo);
else if (!PG_ARGISNULL(2))
appendStringInfoText(state, PG_GETARG_TEXT_PP(2)); /* delimiter */
appendStringInfoText(state, PG_GETARG_TEXT_PP(1)); /* value */
}
/*
* The transition type for string_agg() is declared to be "internal",
* which is a pass-by-value type the same size as a pointer.
*/
PG_RETURN_POINTER(state);
#else
/* ignore if null */
if (!PG_ARGISNULL(1))
{
if (PG_ARGISNULL(0))
return PointerGetDatum(DatumGetTextPCopy(PG_GETARG_DATUM(1)));
else
{
/* delimiter is NULL, so a normal append */
if (PG_ARGISNULL(2))
return DirectFunctionCall2(textcat, PG_GETARG_DATUM(0),
PG_GETARG_DATUM(1));
else
{
/*
* Convoluted, yes, but we might be operating on large amounts
* of memory here. So, be careful to free the intermediate
* memory.
*/
Datum d1 = DirectFunctionCall2(textcat, PG_GETARG_DATUM(2),
PG_GETARG_DATUM(1));
Pointer p = DatumGetPointer(d1);
Datum d2;
d2 = DirectFunctionCall2(textcat, PG_GETARG_DATUM(0), d1);
pfree(p);
PG_RETURN_DATUM(d2);
}
}
}
if (PG_ARGISNULL(0))
PG_RETURN_NULL();
else
PG_RETURN_DATUM(PG_GETARG_DATUM(0));
#endif
}
#if PG_VERSION_NUM >= 80400
static MedianState *
accumFloat4(MedianState *mstate, float4 value, MemoryContext aggcontext)
{
MemoryContext oldcontext;
if (mstate == NULL)
{
/* First call - initialize */
oldcontext = MemoryContextSwitchTo(aggcontext);
mstate = palloc(sizeof(MedianState));
mstate->alen = 1024;
mstate->nextlen = 2 * 1024;
mstate->nelems = 0;
mstate->d.float4_values = palloc(mstate->alen * sizeof(float4));
MemoryContextSwitchTo(oldcontext);
}
else
{
/* enlarge float4_values if needed */
if (mstate->nelems >= mstate->alen)
{
int newlen = mstate->nextlen;
oldcontext = MemoryContextSwitchTo(aggcontext);
mstate->nextlen += mstate->alen;
mstate->alen = newlen;
mstate->d.float4_values = repalloc(mstate->d.float4_values,
mstate->alen * sizeof(float4));
MemoryContextSwitchTo(oldcontext);
}
}
mstate->d.float4_values[mstate->nelems++] = value;
return mstate;
}
static MedianState *
accumFloat8(MedianState *mstate, float8 value, MemoryContext aggcontext)
{
MemoryContext oldcontext;
if (mstate == NULL)
{
/* First call - initialize */
oldcontext = MemoryContextSwitchTo(aggcontext);
mstate = palloc(sizeof(MedianState));
mstate->alen = 1024;
mstate->nextlen = 2 * 1024;
mstate->nelems = 0;
mstate->d.float8_values = palloc(mstate->alen * sizeof(float8));
MemoryContextSwitchTo(oldcontext);
}
else
{
/* enlarge float4_values if needed */
if (mstate->nelems >= mstate->alen)
{
int newlen = mstate->nextlen;
oldcontext = MemoryContextSwitchTo(aggcontext);
mstate->nextlen += mstate->alen;
mstate->alen = newlen;
mstate->d.float8_values = repalloc(mstate->d.float8_values,
mstate->alen * sizeof(float8));
MemoryContextSwitchTo(oldcontext);
}
}
mstate->d.float8_values[mstate->nelems++] = value;
return mstate;
}
#endif
Datum
orafce_median4_transfn(PG_FUNCTION_ARGS)
{
#if PG_VERSION_NUM >= 80400
MemoryContext aggcontext;
MedianState *state = NULL;
float4 elem;
if (!AggCheckCallContext(fcinfo, &aggcontext))
{
/* cannot be called directly because of internal-type argument */
elog(ERROR, "median4_transfn called in non-aggregate context");
}
state = PG_ARGISNULL(0) ? NULL : (MedianState *) PG_GETARG_POINTER(0);
if (PG_ARGISNULL(1))
PG_RETURN_POINTER(state);
elem = PG_GETARG_FLOAT4(1);
state = accumFloat4(state, elem, aggcontext);
PG_RETURN_POINTER(state);
#else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("feature not suppported"),
errdetail("This functions is blocked on PostgreSQL 8.3 and older (from security reasons).")));
PG_RETURN_NULL();
#endif
}
int
orafce_float4_cmp(const void *a, const void *b)
{
return *((float4 *) a) - *((float4*) b);
}
Datum
orafce_median4_finalfn(PG_FUNCTION_ARGS)
{
#if PG_VERSION_NUM >= 80400
MedianState *state = NULL;
int lidx;
int hidx;
float4 result;
if (PG_ARGISNULL(0))
PG_RETURN_NULL();
state = (MedianState *) PG_GETARG_POINTER(0);
qsort(state->d.float4_values, state->nelems, sizeof(float4), orafce_float4_cmp);
lidx = state->nelems / 2 + 1 - 1;
hidx = (state->nelems + 1) / 2 - 1;
if (lidx == hidx)
result = state->d.float4_values[lidx];
else
result = (state->d.float4_values[lidx] + state->d.float4_values[hidx]) / 2.0;
PG_RETURN_FLOAT4(result);
#else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("feature not suppported"),
errdetail("This functions is blocked on PostgreSQL 8.3 and older (from security reasons).")));
PG_RETURN_NULL();
#endif
}
Datum
orafce_median8_transfn(PG_FUNCTION_ARGS)
{
#if PG_VERSION_NUM >= 80400
MemoryContext aggcontext;
MedianState *state = NULL;
float8 elem;
if (!AggCheckCallContext(fcinfo, &aggcontext))
{
/* cannot be called directly because of internal-type argument */
elog(ERROR, "median4_transfn called in non-aggregate context");
}
state = PG_ARGISNULL(0) ? NULL : (MedianState *) PG_GETARG_POINTER(0);
if (PG_ARGISNULL(1))
PG_RETURN_POINTER(state);
elem = PG_GETARG_FLOAT8(1);
state = accumFloat8(state, elem, aggcontext);
PG_RETURN_POINTER(state);
#else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("feature not suppported"),
errdetail("This functions is blocked on PostgreSQL 8.3 and older (from security reasons).")));
PG_RETURN_NULL();
#endif
}
int
orafce_float8_cmp(const void *a, const void *b)
{
return *((float8 *) a) - *((float8*) b);
}
Datum
orafce_median8_finalfn(PG_FUNCTION_ARGS)
{
#if PG_VERSION_NUM >= 80400
MedianState *state = NULL;
int lidx;
int hidx;
float8 result;
if (PG_ARGISNULL(0))
PG_RETURN_NULL();
state = (MedianState *) PG_GETARG_POINTER(0);
qsort(state->d.float8_values, state->nelems, sizeof(float8), orafce_float8_cmp);
lidx = state->nelems / 2 + 1 - 1;
hidx = (state->nelems + 1) / 2 - 1;
if (lidx == hidx)
result = state->d.float8_values[lidx];
else
result = (state->d.float8_values[lidx] + state->d.float8_values[hidx]) / 2.0;
PG_RETURN_FLOAT8(result);
#else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("feature not suppported"),
errdetail("This functions is blocked on PostgreSQL 8.3 and older (from security reasons).")));
PG_RETURN_NULL();
#endif
}