mirror of
https://github.com/postgres/postgres.git
synced 2025-04-24 10:47:04 +03:00
This reverts commit 608fd198def5390c3490bfe903730207dfd8eeb4. On 2nd thoughts, the StringInfo API requires that strings are NUL terminated and pointing directly to the data in a bytea Datum isn't NUL terminated. Discussion: https://postgr.es/m/CAApHDvorfO3iBZ=xpiZvp3uHtJVLyFaPBSvcAhAq2HPLnaNSwQ@mail.gmail.com
1702 lines
44 KiB
C
1702 lines
44 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* array_userfuncs.c
|
|
* Misc user-visible array support functions
|
|
*
|
|
* Copyright (c) 2003-2023, PostgreSQL Global Development Group
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/utils/adt/array_userfuncs.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "catalog/pg_type.h"
|
|
#include "libpq/pqformat.h"
|
|
#include "common/int.h"
|
|
#include "common/pg_prng.h"
|
|
#include "port/pg_bitutils.h"
|
|
#include "utils/array.h"
|
|
#include "utils/datum.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/typcache.h"
|
|
|
|
/*
|
|
* SerialIOData
|
|
* Used for caching element-type data in array_agg_serialize
|
|
*/
|
|
typedef struct SerialIOData
|
|
{
|
|
FmgrInfo typsend;
|
|
} SerialIOData;
|
|
|
|
/*
|
|
* DeserialIOData
|
|
* Used for caching element-type data in array_agg_deserialize
|
|
*/
|
|
typedef struct DeserialIOData
|
|
{
|
|
FmgrInfo typreceive;
|
|
Oid typioparam;
|
|
} DeserialIOData;
|
|
|
|
static Datum array_position_common(FunctionCallInfo fcinfo);
|
|
|
|
|
|
/*
|
|
* fetch_array_arg_replace_nulls
|
|
*
|
|
* Fetch an array-valued argument in expanded form; if it's null, construct an
|
|
* empty array value of the proper data type. Also cache basic element type
|
|
* information in fn_extra.
|
|
*
|
|
* Caution: if the input is a read/write pointer, this returns the input
|
|
* argument; so callers must be sure that their changes are "safe", that is
|
|
* they cannot leave the array in a corrupt state.
|
|
*
|
|
* If we're being called as an aggregate function, make sure any newly-made
|
|
* expanded array is allocated in the aggregate state context, so as to save
|
|
* copying operations.
|
|
*/
|
|
static ExpandedArrayHeader *
|
|
fetch_array_arg_replace_nulls(FunctionCallInfo fcinfo, int argno)
|
|
{
|
|
ExpandedArrayHeader *eah;
|
|
Oid element_type;
|
|
ArrayMetaState *my_extra;
|
|
MemoryContext resultcxt;
|
|
|
|
/* If first time through, create datatype cache struct */
|
|
my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
|
|
if (my_extra == NULL)
|
|
{
|
|
my_extra = (ArrayMetaState *)
|
|
MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
|
|
sizeof(ArrayMetaState));
|
|
my_extra->element_type = InvalidOid;
|
|
fcinfo->flinfo->fn_extra = my_extra;
|
|
}
|
|
|
|
/* Figure out which context we want the result in */
|
|
if (!AggCheckCallContext(fcinfo, &resultcxt))
|
|
resultcxt = CurrentMemoryContext;
|
|
|
|
/* Now collect the array value */
|
|
if (!PG_ARGISNULL(argno))
|
|
{
|
|
MemoryContext oldcxt = MemoryContextSwitchTo(resultcxt);
|
|
|
|
eah = PG_GETARG_EXPANDED_ARRAYX(argno, my_extra);
|
|
MemoryContextSwitchTo(oldcxt);
|
|
}
|
|
else
|
|
{
|
|
/* We have to look up the array type and element type */
|
|
Oid arr_typeid = get_fn_expr_argtype(fcinfo->flinfo, argno);
|
|
|
|
if (!OidIsValid(arr_typeid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("could not determine input data type")));
|
|
element_type = get_element_type(arr_typeid);
|
|
if (!OidIsValid(element_type))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
errmsg("input data type is not an array")));
|
|
|
|
eah = construct_empty_expanded_array(element_type,
|
|
resultcxt,
|
|
my_extra);
|
|
}
|
|
|
|
return eah;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
* array_append :
|
|
* push an element onto the end of a one-dimensional array
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
Datum
|
|
array_append(PG_FUNCTION_ARGS)
|
|
{
|
|
ExpandedArrayHeader *eah;
|
|
Datum newelem;
|
|
bool isNull;
|
|
Datum result;
|
|
int *dimv,
|
|
*lb;
|
|
int indx;
|
|
ArrayMetaState *my_extra;
|
|
|
|
eah = fetch_array_arg_replace_nulls(fcinfo, 0);
|
|
isNull = PG_ARGISNULL(1);
|
|
if (isNull)
|
|
newelem = (Datum) 0;
|
|
else
|
|
newelem = PG_GETARG_DATUM(1);
|
|
|
|
if (eah->ndims == 1)
|
|
{
|
|
/* append newelem */
|
|
lb = eah->lbound;
|
|
dimv = eah->dims;
|
|
|
|
/* index of added elem is at lb[0] + (dimv[0] - 1) + 1 */
|
|
if (pg_add_s32_overflow(lb[0], dimv[0], &indx))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("integer out of range")));
|
|
}
|
|
else if (eah->ndims == 0)
|
|
indx = 1;
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
|
errmsg("argument must be empty or one-dimensional array")));
|
|
|
|
/* Perform element insertion */
|
|
my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
|
|
|
|
result = array_set_element(EOHPGetRWDatum(&eah->hdr),
|
|
1, &indx, newelem, isNull,
|
|
-1, my_extra->typlen, my_extra->typbyval, my_extra->typalign);
|
|
|
|
PG_RETURN_DATUM(result);
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
* array_prepend :
|
|
* push an element onto the front of a one-dimensional array
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
Datum
|
|
array_prepend(PG_FUNCTION_ARGS)
|
|
{
|
|
ExpandedArrayHeader *eah;
|
|
Datum newelem;
|
|
bool isNull;
|
|
Datum result;
|
|
int *lb;
|
|
int indx;
|
|
int lb0;
|
|
ArrayMetaState *my_extra;
|
|
|
|
isNull = PG_ARGISNULL(0);
|
|
if (isNull)
|
|
newelem = (Datum) 0;
|
|
else
|
|
newelem = PG_GETARG_DATUM(0);
|
|
eah = fetch_array_arg_replace_nulls(fcinfo, 1);
|
|
|
|
if (eah->ndims == 1)
|
|
{
|
|
/* prepend newelem */
|
|
lb = eah->lbound;
|
|
lb0 = lb[0];
|
|
|
|
if (pg_sub_s32_overflow(lb0, 1, &indx))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("integer out of range")));
|
|
}
|
|
else if (eah->ndims == 0)
|
|
{
|
|
indx = 1;
|
|
lb0 = 1;
|
|
}
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
|
errmsg("argument must be empty or one-dimensional array")));
|
|
|
|
/* Perform element insertion */
|
|
my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
|
|
|
|
result = array_set_element(EOHPGetRWDatum(&eah->hdr),
|
|
1, &indx, newelem, isNull,
|
|
-1, my_extra->typlen, my_extra->typbyval, my_extra->typalign);
|
|
|
|
/* Readjust result's LB to match the input's, as expected for prepend */
|
|
Assert(result == EOHPGetRWDatum(&eah->hdr));
|
|
if (eah->ndims == 1)
|
|
{
|
|
/* This is ok whether we've deconstructed or not */
|
|
eah->lbound[0] = lb0;
|
|
}
|
|
|
|
PG_RETURN_DATUM(result);
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
* array_cat :
|
|
* concatenate two nD arrays to form an nD array, or
|
|
* push an (n-1)D array onto the end of an nD array
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
Datum
|
|
array_cat(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *v1,
|
|
*v2;
|
|
ArrayType *result;
|
|
int *dims,
|
|
*lbs,
|
|
ndims,
|
|
nitems,
|
|
ndatabytes,
|
|
nbytes;
|
|
int *dims1,
|
|
*lbs1,
|
|
ndims1,
|
|
nitems1,
|
|
ndatabytes1;
|
|
int *dims2,
|
|
*lbs2,
|
|
ndims2,
|
|
nitems2,
|
|
ndatabytes2;
|
|
int i;
|
|
char *dat1,
|
|
*dat2;
|
|
bits8 *bitmap1,
|
|
*bitmap2;
|
|
Oid element_type;
|
|
Oid element_type1;
|
|
Oid element_type2;
|
|
int32 dataoffset;
|
|
|
|
/* Concatenating a null array is a no-op, just return the other input */
|
|
if (PG_ARGISNULL(0))
|
|
{
|
|
if (PG_ARGISNULL(1))
|
|
PG_RETURN_NULL();
|
|
result = PG_GETARG_ARRAYTYPE_P(1);
|
|
PG_RETURN_ARRAYTYPE_P(result);
|
|
}
|
|
if (PG_ARGISNULL(1))
|
|
{
|
|
result = PG_GETARG_ARRAYTYPE_P(0);
|
|
PG_RETURN_ARRAYTYPE_P(result);
|
|
}
|
|
|
|
v1 = PG_GETARG_ARRAYTYPE_P(0);
|
|
v2 = PG_GETARG_ARRAYTYPE_P(1);
|
|
|
|
element_type1 = ARR_ELEMTYPE(v1);
|
|
element_type2 = ARR_ELEMTYPE(v2);
|
|
|
|
/* Check we have matching element types */
|
|
if (element_type1 != element_type2)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
errmsg("cannot concatenate incompatible arrays"),
|
|
errdetail("Arrays with element types %s and %s are not "
|
|
"compatible for concatenation.",
|
|
format_type_be(element_type1),
|
|
format_type_be(element_type2))));
|
|
|
|
/* OK, use it */
|
|
element_type = element_type1;
|
|
|
|
/*----------
|
|
* We must have one of the following combinations of inputs:
|
|
* 1) one empty array, and one non-empty array
|
|
* 2) both arrays empty
|
|
* 3) two arrays with ndims1 == ndims2
|
|
* 4) ndims1 == ndims2 - 1
|
|
* 5) ndims1 == ndims2 + 1
|
|
*----------
|
|
*/
|
|
ndims1 = ARR_NDIM(v1);
|
|
ndims2 = ARR_NDIM(v2);
|
|
|
|
/*
|
|
* short circuit - if one input array is empty, and the other is not, we
|
|
* return the non-empty one as the result
|
|
*
|
|
* if both are empty, return the first one
|
|
*/
|
|
if (ndims1 == 0 && ndims2 > 0)
|
|
PG_RETURN_ARRAYTYPE_P(v2);
|
|
|
|
if (ndims2 == 0)
|
|
PG_RETURN_ARRAYTYPE_P(v1);
|
|
|
|
/* the rest fall under rule 3, 4, or 5 */
|
|
if (ndims1 != ndims2 &&
|
|
ndims1 != ndims2 - 1 &&
|
|
ndims1 != ndims2 + 1)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
|
errmsg("cannot concatenate incompatible arrays"),
|
|
errdetail("Arrays of %d and %d dimensions are not "
|
|
"compatible for concatenation.",
|
|
ndims1, ndims2)));
|
|
|
|
/* get argument array details */
|
|
lbs1 = ARR_LBOUND(v1);
|
|
lbs2 = ARR_LBOUND(v2);
|
|
dims1 = ARR_DIMS(v1);
|
|
dims2 = ARR_DIMS(v2);
|
|
dat1 = ARR_DATA_PTR(v1);
|
|
dat2 = ARR_DATA_PTR(v2);
|
|
bitmap1 = ARR_NULLBITMAP(v1);
|
|
bitmap2 = ARR_NULLBITMAP(v2);
|
|
nitems1 = ArrayGetNItems(ndims1, dims1);
|
|
nitems2 = ArrayGetNItems(ndims2, dims2);
|
|
ndatabytes1 = ARR_SIZE(v1) - ARR_DATA_OFFSET(v1);
|
|
ndatabytes2 = ARR_SIZE(v2) - ARR_DATA_OFFSET(v2);
|
|
|
|
if (ndims1 == ndims2)
|
|
{
|
|
/*
|
|
* resulting array is made up of the elements (possibly arrays
|
|
* themselves) of the input argument arrays
|
|
*/
|
|
ndims = ndims1;
|
|
dims = (int *) palloc(ndims * sizeof(int));
|
|
lbs = (int *) palloc(ndims * sizeof(int));
|
|
|
|
dims[0] = dims1[0] + dims2[0];
|
|
lbs[0] = lbs1[0];
|
|
|
|
for (i = 1; i < ndims; i++)
|
|
{
|
|
if (dims1[i] != dims2[i] || lbs1[i] != lbs2[i])
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
|
errmsg("cannot concatenate incompatible arrays"),
|
|
errdetail("Arrays with differing element dimensions are "
|
|
"not compatible for concatenation.")));
|
|
|
|
dims[i] = dims1[i];
|
|
lbs[i] = lbs1[i];
|
|
}
|
|
}
|
|
else if (ndims1 == ndims2 - 1)
|
|
{
|
|
/*
|
|
* resulting array has the second argument as the outer array, with
|
|
* the first argument inserted at the front of the outer dimension
|
|
*/
|
|
ndims = ndims2;
|
|
dims = (int *) palloc(ndims * sizeof(int));
|
|
lbs = (int *) palloc(ndims * sizeof(int));
|
|
memcpy(dims, dims2, ndims * sizeof(int));
|
|
memcpy(lbs, lbs2, ndims * sizeof(int));
|
|
|
|
/* increment number of elements in outer array */
|
|
dims[0] += 1;
|
|
|
|
/* make sure the added element matches our existing elements */
|
|
for (i = 0; i < ndims1; i++)
|
|
{
|
|
if (dims1[i] != dims[i + 1] || lbs1[i] != lbs[i + 1])
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
|
errmsg("cannot concatenate incompatible arrays"),
|
|
errdetail("Arrays with differing dimensions are not "
|
|
"compatible for concatenation.")));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* (ndims1 == ndims2 + 1)
|
|
*
|
|
* resulting array has the first argument as the outer array, with the
|
|
* second argument appended to the end of the outer dimension
|
|
*/
|
|
ndims = ndims1;
|
|
dims = (int *) palloc(ndims * sizeof(int));
|
|
lbs = (int *) palloc(ndims * sizeof(int));
|
|
memcpy(dims, dims1, ndims * sizeof(int));
|
|
memcpy(lbs, lbs1, ndims * sizeof(int));
|
|
|
|
/* increment number of elements in outer array */
|
|
dims[0] += 1;
|
|
|
|
/* make sure the added element matches our existing elements */
|
|
for (i = 0; i < ndims2; i++)
|
|
{
|
|
if (dims2[i] != dims[i + 1] || lbs2[i] != lbs[i + 1])
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
|
errmsg("cannot concatenate incompatible arrays"),
|
|
errdetail("Arrays with differing dimensions are not "
|
|
"compatible for concatenation.")));
|
|
}
|
|
}
|
|
|
|
/* Do this mainly for overflow checking */
|
|
nitems = ArrayGetNItems(ndims, dims);
|
|
ArrayCheckBounds(ndims, dims, lbs);
|
|
|
|
/* build the result array */
|
|
ndatabytes = ndatabytes1 + ndatabytes2;
|
|
if (ARR_HASNULL(v1) || ARR_HASNULL(v2))
|
|
{
|
|
dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems);
|
|
nbytes = ndatabytes + dataoffset;
|
|
}
|
|
else
|
|
{
|
|
dataoffset = 0; /* marker for no null bitmap */
|
|
nbytes = ndatabytes + ARR_OVERHEAD_NONULLS(ndims);
|
|
}
|
|
result = (ArrayType *) palloc0(nbytes);
|
|
SET_VARSIZE(result, nbytes);
|
|
result->ndim = ndims;
|
|
result->dataoffset = dataoffset;
|
|
result->elemtype = element_type;
|
|
memcpy(ARR_DIMS(result), dims, ndims * sizeof(int));
|
|
memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int));
|
|
/* data area is arg1 then arg2 */
|
|
memcpy(ARR_DATA_PTR(result), dat1, ndatabytes1);
|
|
memcpy(ARR_DATA_PTR(result) + ndatabytes1, dat2, ndatabytes2);
|
|
/* handle the null bitmap if needed */
|
|
if (ARR_HASNULL(result))
|
|
{
|
|
array_bitmap_copy(ARR_NULLBITMAP(result), 0,
|
|
bitmap1, 0,
|
|
nitems1);
|
|
array_bitmap_copy(ARR_NULLBITMAP(result), nitems1,
|
|
bitmap2, 0,
|
|
nitems2);
|
|
}
|
|
|
|
PG_RETURN_ARRAYTYPE_P(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* ARRAY_AGG(anynonarray) aggregate function
|
|
*/
|
|
Datum
|
|
array_agg_transfn(PG_FUNCTION_ARGS)
|
|
{
|
|
Oid arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
|
|
MemoryContext aggcontext;
|
|
ArrayBuildState *state;
|
|
Datum elem;
|
|
|
|
if (arg1_typeid == InvalidOid)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("could not determine input data type")));
|
|
|
|
/*
|
|
* Note: we do not need a run-time check about whether arg1_typeid is a
|
|
* valid array element type, because the parser would have verified that
|
|
* while resolving the input/result types of this polymorphic aggregate.
|
|
*/
|
|
|
|
if (!AggCheckCallContext(fcinfo, &aggcontext))
|
|
{
|
|
/* cannot be called directly because of internal-type argument */
|
|
elog(ERROR, "array_agg_transfn called in non-aggregate context");
|
|
}
|
|
|
|
if (PG_ARGISNULL(0))
|
|
state = initArrayResult(arg1_typeid, aggcontext, false);
|
|
else
|
|
state = (ArrayBuildState *) PG_GETARG_POINTER(0);
|
|
|
|
elem = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1);
|
|
|
|
state = accumArrayResult(state,
|
|
elem,
|
|
PG_ARGISNULL(1),
|
|
arg1_typeid,
|
|
aggcontext);
|
|
|
|
/*
|
|
* The transition type for array_agg() is declared to be "internal", which
|
|
* is a pass-by-value type the same size as a pointer. So we can safely
|
|
* pass the ArrayBuildState pointer through nodeAgg.c's machinations.
|
|
*/
|
|
PG_RETURN_POINTER(state);
|
|
}
|
|
|
|
Datum
|
|
array_agg_combine(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayBuildState *state1;
|
|
ArrayBuildState *state2;
|
|
MemoryContext agg_context;
|
|
MemoryContext old_context;
|
|
|
|
if (!AggCheckCallContext(fcinfo, &agg_context))
|
|
elog(ERROR, "aggregate function called in non-aggregate context");
|
|
|
|
state1 = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
|
|
state2 = PG_ARGISNULL(1) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(1);
|
|
|
|
if (state2 == NULL)
|
|
{
|
|
/*
|
|
* NULL state2 is easy, just return state1, which we know is already
|
|
* in the agg_context
|
|
*/
|
|
if (state1 == NULL)
|
|
PG_RETURN_NULL();
|
|
PG_RETURN_POINTER(state1);
|
|
}
|
|
|
|
if (state1 == NULL)
|
|
{
|
|
/* We must copy state2's data into the agg_context */
|
|
state1 = initArrayResultWithSize(state2->element_type, agg_context,
|
|
false, state2->alen);
|
|
|
|
old_context = MemoryContextSwitchTo(agg_context);
|
|
|
|
for (int i = 0; i < state2->nelems; i++)
|
|
{
|
|
if (!state2->dnulls[i])
|
|
state1->dvalues[i] = datumCopy(state2->dvalues[i],
|
|
state1->typbyval,
|
|
state1->typlen);
|
|
else
|
|
state1->dvalues[i] = (Datum) 0;
|
|
}
|
|
|
|
MemoryContextSwitchTo(old_context);
|
|
|
|
memcpy(state1->dnulls, state2->dnulls, sizeof(bool) * state2->nelems);
|
|
|
|
state1->nelems = state2->nelems;
|
|
|
|
PG_RETURN_POINTER(state1);
|
|
}
|
|
else if (state2->nelems > 0)
|
|
{
|
|
/* We only need to combine the two states if state2 has any elements */
|
|
int reqsize = state1->nelems + state2->nelems;
|
|
MemoryContext oldContext = MemoryContextSwitchTo(state1->mcontext);
|
|
|
|
Assert(state1->element_type == state2->element_type);
|
|
|
|
/* Enlarge state1 arrays if needed */
|
|
if (state1->alen < reqsize)
|
|
{
|
|
/* Use a power of 2 size rather than allocating just reqsize */
|
|
state1->alen = pg_nextpower2_32(reqsize);
|
|
state1->dvalues = (Datum *) repalloc(state1->dvalues,
|
|
state1->alen * sizeof(Datum));
|
|
state1->dnulls = (bool *) repalloc(state1->dnulls,
|
|
state1->alen * sizeof(bool));
|
|
}
|
|
|
|
/* Copy in the state2 elements to the end of the state1 arrays */
|
|
for (int i = 0; i < state2->nelems; i++)
|
|
{
|
|
if (!state2->dnulls[i])
|
|
state1->dvalues[i + state1->nelems] =
|
|
datumCopy(state2->dvalues[i],
|
|
state1->typbyval,
|
|
state1->typlen);
|
|
else
|
|
state1->dvalues[i + state1->nelems] = (Datum) 0;
|
|
}
|
|
|
|
memcpy(&state1->dnulls[state1->nelems], state2->dnulls,
|
|
sizeof(bool) * state2->nelems);
|
|
|
|
state1->nelems = reqsize;
|
|
|
|
MemoryContextSwitchTo(oldContext);
|
|
}
|
|
|
|
PG_RETURN_POINTER(state1);
|
|
}
|
|
|
|
/*
|
|
* array_agg_serialize
|
|
* Serialize ArrayBuildState into bytea.
|
|
*/
|
|
Datum
|
|
array_agg_serialize(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayBuildState *state;
|
|
StringInfoData buf;
|
|
bytea *result;
|
|
|
|
/* cannot be called directly because of internal-type argument */
|
|
Assert(AggCheckCallContext(fcinfo, NULL));
|
|
|
|
state = (ArrayBuildState *) PG_GETARG_POINTER(0);
|
|
|
|
pq_begintypsend(&buf);
|
|
|
|
/*
|
|
* element_type. Putting this first is more convenient in deserialization
|
|
*/
|
|
pq_sendint32(&buf, state->element_type);
|
|
|
|
/*
|
|
* nelems -- send first so we know how large to make the dvalues and
|
|
* dnulls array during deserialization.
|
|
*/
|
|
pq_sendint64(&buf, state->nelems);
|
|
|
|
/* alen can be decided during deserialization */
|
|
|
|
/* typlen */
|
|
pq_sendint16(&buf, state->typlen);
|
|
|
|
/* typbyval */
|
|
pq_sendbyte(&buf, state->typbyval);
|
|
|
|
/* typalign */
|
|
pq_sendbyte(&buf, state->typalign);
|
|
|
|
/* dnulls */
|
|
pq_sendbytes(&buf, state->dnulls, sizeof(bool) * state->nelems);
|
|
|
|
/*
|
|
* dvalues. By agreement with array_agg_deserialize, when the element
|
|
* type is byval, we just transmit the Datum array as-is, including any
|
|
* null elements. For by-ref types, we must invoke the element type's
|
|
* send function, and we skip null elements (which is why the nulls flags
|
|
* must be sent first).
|
|
*/
|
|
if (state->typbyval)
|
|
pq_sendbytes(&buf, state->dvalues, sizeof(Datum) * state->nelems);
|
|
else
|
|
{
|
|
SerialIOData *iodata;
|
|
int i;
|
|
|
|
/* Avoid repeat catalog lookups for typsend function */
|
|
iodata = (SerialIOData *) fcinfo->flinfo->fn_extra;
|
|
if (iodata == NULL)
|
|
{
|
|
Oid typsend;
|
|
bool typisvarlena;
|
|
|
|
iodata = (SerialIOData *)
|
|
MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
|
|
sizeof(SerialIOData));
|
|
getTypeBinaryOutputInfo(state->element_type, &typsend,
|
|
&typisvarlena);
|
|
fmgr_info_cxt(typsend, &iodata->typsend,
|
|
fcinfo->flinfo->fn_mcxt);
|
|
fcinfo->flinfo->fn_extra = (void *) iodata;
|
|
}
|
|
|
|
for (i = 0; i < state->nelems; i++)
|
|
{
|
|
bytea *outputbytes;
|
|
|
|
if (state->dnulls[i])
|
|
continue;
|
|
outputbytes = SendFunctionCall(&iodata->typsend,
|
|
state->dvalues[i]);
|
|
pq_sendint32(&buf, VARSIZE(outputbytes) - VARHDRSZ);
|
|
pq_sendbytes(&buf, VARDATA(outputbytes),
|
|
VARSIZE(outputbytes) - VARHDRSZ);
|
|
}
|
|
}
|
|
|
|
result = pq_endtypsend(&buf);
|
|
|
|
PG_RETURN_BYTEA_P(result);
|
|
}
|
|
|
|
Datum
|
|
array_agg_deserialize(PG_FUNCTION_ARGS)
|
|
{
|
|
bytea *sstate;
|
|
ArrayBuildState *result;
|
|
StringInfoData buf;
|
|
Oid element_type;
|
|
int64 nelems;
|
|
const char *temp;
|
|
|
|
if (!AggCheckCallContext(fcinfo, NULL))
|
|
elog(ERROR, "aggregate function called in non-aggregate context");
|
|
|
|
sstate = PG_GETARG_BYTEA_PP(0);
|
|
|
|
/*
|
|
* Copy the bytea into a StringInfo so that we can "receive" it using the
|
|
* standard recv-function infrastructure.
|
|
*/
|
|
initStringInfo(&buf);
|
|
appendBinaryStringInfo(&buf,
|
|
VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate));
|
|
|
|
/* element_type */
|
|
element_type = pq_getmsgint(&buf, 4);
|
|
|
|
/* nelems */
|
|
nelems = pq_getmsgint64(&buf);
|
|
|
|
/* Create output ArrayBuildState with the needed number of elements */
|
|
result = initArrayResultWithSize(element_type, CurrentMemoryContext,
|
|
false, nelems);
|
|
result->nelems = nelems;
|
|
|
|
/* typlen */
|
|
result->typlen = pq_getmsgint(&buf, 2);
|
|
|
|
/* typbyval */
|
|
result->typbyval = pq_getmsgbyte(&buf);
|
|
|
|
/* typalign */
|
|
result->typalign = pq_getmsgbyte(&buf);
|
|
|
|
/* dnulls */
|
|
temp = pq_getmsgbytes(&buf, sizeof(bool) * nelems);
|
|
memcpy(result->dnulls, temp, sizeof(bool) * nelems);
|
|
|
|
/* dvalues --- see comment in array_agg_serialize */
|
|
if (result->typbyval)
|
|
{
|
|
temp = pq_getmsgbytes(&buf, sizeof(Datum) * nelems);
|
|
memcpy(result->dvalues, temp, sizeof(Datum) * nelems);
|
|
}
|
|
else
|
|
{
|
|
DeserialIOData *iodata;
|
|
|
|
/* Avoid repeat catalog lookups for typreceive function */
|
|
iodata = (DeserialIOData *) fcinfo->flinfo->fn_extra;
|
|
if (iodata == NULL)
|
|
{
|
|
Oid typreceive;
|
|
|
|
iodata = (DeserialIOData *)
|
|
MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
|
|
sizeof(DeserialIOData));
|
|
getTypeBinaryInputInfo(element_type, &typreceive,
|
|
&iodata->typioparam);
|
|
fmgr_info_cxt(typreceive, &iodata->typreceive,
|
|
fcinfo->flinfo->fn_mcxt);
|
|
fcinfo->flinfo->fn_extra = (void *) iodata;
|
|
}
|
|
|
|
for (int i = 0; i < nelems; i++)
|
|
{
|
|
int itemlen;
|
|
StringInfoData elem_buf;
|
|
char csave;
|
|
|
|
if (result->dnulls[i])
|
|
{
|
|
result->dvalues[i] = (Datum) 0;
|
|
continue;
|
|
}
|
|
|
|
itemlen = pq_getmsgint(&buf, 4);
|
|
if (itemlen < 0 || itemlen > (buf.len - buf.cursor))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
|
|
errmsg("insufficient data left in message")));
|
|
|
|
/*
|
|
* Rather than copying data around, we just set up a phony
|
|
* StringInfo pointing to the correct portion of the input buffer.
|
|
* We assume we can scribble on the input buffer so as to maintain
|
|
* the convention that StringInfos have a trailing null.
|
|
*/
|
|
elem_buf.data = &buf.data[buf.cursor];
|
|
elem_buf.maxlen = itemlen + 1;
|
|
elem_buf.len = itemlen;
|
|
elem_buf.cursor = 0;
|
|
|
|
buf.cursor += itemlen;
|
|
|
|
csave = buf.data[buf.cursor];
|
|
buf.data[buf.cursor] = '\0';
|
|
|
|
/* Now call the element's receiveproc */
|
|
result->dvalues[i] = ReceiveFunctionCall(&iodata->typreceive,
|
|
&elem_buf,
|
|
iodata->typioparam,
|
|
-1);
|
|
|
|
buf.data[buf.cursor] = csave;
|
|
}
|
|
}
|
|
|
|
pq_getmsgend(&buf);
|
|
pfree(buf.data);
|
|
|
|
PG_RETURN_POINTER(result);
|
|
}
|
|
|
|
Datum
|
|
array_agg_finalfn(PG_FUNCTION_ARGS)
|
|
{
|
|
Datum result;
|
|
ArrayBuildState *state;
|
|
int dims[1];
|
|
int lbs[1];
|
|
|
|
/* cannot be called directly because of internal-type argument */
|
|
Assert(AggCheckCallContext(fcinfo, NULL));
|
|
|
|
state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
|
|
|
|
if (state == NULL)
|
|
PG_RETURN_NULL(); /* returns null iff no input values */
|
|
|
|
dims[0] = state->nelems;
|
|
lbs[0] = 1;
|
|
|
|
/*
|
|
* Make the result. We cannot release the ArrayBuildState because
|
|
* sometimes aggregate final functions are re-executed. Rather, it is
|
|
* nodeAgg.c's responsibility to reset the aggcontext when it's safe to do
|
|
* so.
|
|
*/
|
|
result = makeMdArrayResult(state, 1, dims, lbs,
|
|
CurrentMemoryContext,
|
|
false);
|
|
|
|
PG_RETURN_DATUM(result);
|
|
}
|
|
|
|
/*
|
|
* ARRAY_AGG(anyarray) aggregate function
|
|
*/
|
|
Datum
|
|
array_agg_array_transfn(PG_FUNCTION_ARGS)
|
|
{
|
|
Oid arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
|
|
MemoryContext aggcontext;
|
|
ArrayBuildStateArr *state;
|
|
|
|
if (arg1_typeid == InvalidOid)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("could not determine input data type")));
|
|
|
|
/*
|
|
* Note: we do not need a run-time check about whether arg1_typeid is a
|
|
* valid array type, because the parser would have verified that while
|
|
* resolving the input/result types of this polymorphic aggregate.
|
|
*/
|
|
|
|
if (!AggCheckCallContext(fcinfo, &aggcontext))
|
|
{
|
|
/* cannot be called directly because of internal-type argument */
|
|
elog(ERROR, "array_agg_array_transfn called in non-aggregate context");
|
|
}
|
|
|
|
|
|
if (PG_ARGISNULL(0))
|
|
state = initArrayResultArr(arg1_typeid, InvalidOid, aggcontext, false);
|
|
else
|
|
state = (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
|
|
|
|
state = accumArrayResultArr(state,
|
|
PG_GETARG_DATUM(1),
|
|
PG_ARGISNULL(1),
|
|
arg1_typeid,
|
|
aggcontext);
|
|
|
|
/*
|
|
* The transition type for array_agg() is declared to be "internal", which
|
|
* is a pass-by-value type the same size as a pointer. So we can safely
|
|
* pass the ArrayBuildStateArr pointer through nodeAgg.c's machinations.
|
|
*/
|
|
PG_RETURN_POINTER(state);
|
|
}
|
|
|
|
Datum
|
|
array_agg_array_combine(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayBuildStateArr *state1;
|
|
ArrayBuildStateArr *state2;
|
|
MemoryContext agg_context;
|
|
MemoryContext old_context;
|
|
|
|
if (!AggCheckCallContext(fcinfo, &agg_context))
|
|
elog(ERROR, "aggregate function called in non-aggregate context");
|
|
|
|
state1 = PG_ARGISNULL(0) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
|
|
state2 = PG_ARGISNULL(1) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(1);
|
|
|
|
if (state2 == NULL)
|
|
{
|
|
/*
|
|
* NULL state2 is easy, just return state1, which we know is already
|
|
* in the agg_context
|
|
*/
|
|
if (state1 == NULL)
|
|
PG_RETURN_NULL();
|
|
PG_RETURN_POINTER(state1);
|
|
}
|
|
|
|
if (state1 == NULL)
|
|
{
|
|
/* We must copy state2's data into the agg_context */
|
|
old_context = MemoryContextSwitchTo(agg_context);
|
|
|
|
state1 = initArrayResultArr(state2->array_type, InvalidOid,
|
|
agg_context, false);
|
|
|
|
state1->abytes = state2->abytes;
|
|
state1->data = (char *) palloc(state1->abytes);
|
|
|
|
if (state2->nullbitmap)
|
|
{
|
|
int size = (state2->aitems + 7) / 8;
|
|
|
|
state1->nullbitmap = (bits8 *) palloc(size);
|
|
memcpy(state1->nullbitmap, state2->nullbitmap, size);
|
|
}
|
|
|
|
memcpy(state1->data, state2->data, state2->nbytes);
|
|
state1->nbytes = state2->nbytes;
|
|
state1->aitems = state2->aitems;
|
|
state1->nitems = state2->nitems;
|
|
state1->ndims = state2->ndims;
|
|
memcpy(state1->dims, state2->dims, sizeof(state2->dims));
|
|
memcpy(state1->lbs, state2->lbs, sizeof(state2->lbs));
|
|
state1->array_type = state2->array_type;
|
|
state1->element_type = state2->element_type;
|
|
|
|
MemoryContextSwitchTo(old_context);
|
|
|
|
PG_RETURN_POINTER(state1);
|
|
}
|
|
|
|
/* We only need to combine the two states if state2 has any items */
|
|
else if (state2->nitems > 0)
|
|
{
|
|
MemoryContext oldContext;
|
|
int reqsize = state1->nbytes + state2->nbytes;
|
|
int i;
|
|
|
|
/*
|
|
* Check the states are compatible with each other. Ensure we use the
|
|
* same error messages that are listed in accumArrayResultArr so that
|
|
* the same error is shown as would have been if we'd not used the
|
|
* combine function for the aggregation.
|
|
*/
|
|
if (state1->ndims != state2->ndims)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
|
errmsg("cannot accumulate arrays of different dimensionality")));
|
|
|
|
/* Check dimensions match ignoring the first dimension. */
|
|
for (i = 1; i < state1->ndims; i++)
|
|
{
|
|
if (state1->dims[i] != state2->dims[i] || state1->lbs[i] != state2->lbs[i])
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
|
errmsg("cannot accumulate arrays of different dimensionality")));
|
|
}
|
|
|
|
|
|
oldContext = MemoryContextSwitchTo(state1->mcontext);
|
|
|
|
/*
|
|
* If there's not enough space in state1 then we'll need to reallocate
|
|
* more.
|
|
*/
|
|
if (state1->abytes < reqsize)
|
|
{
|
|
/* use a power of 2 size rather than allocating just reqsize */
|
|
state1->abytes = pg_nextpower2_32(reqsize);
|
|
state1->data = (char *) repalloc(state1->data, state1->abytes);
|
|
}
|
|
|
|
if (state2->nullbitmap)
|
|
{
|
|
int newnitems = state1->nitems + state2->nitems;
|
|
|
|
if (state1->nullbitmap == NULL)
|
|
{
|
|
/*
|
|
* First input with nulls; we must retrospectively handle any
|
|
* previous inputs by marking all their items non-null.
|
|
*/
|
|
state1->aitems = pg_nextpower2_32(Max(256, newnitems + 1));
|
|
state1->nullbitmap = (bits8 *) palloc((state1->aitems + 7) / 8);
|
|
array_bitmap_copy(state1->nullbitmap, 0,
|
|
NULL, 0,
|
|
state1->nitems);
|
|
}
|
|
else if (newnitems > state1->aitems)
|
|
{
|
|
int newaitems = state1->aitems + state2->aitems;
|
|
|
|
state1->aitems = pg_nextpower2_32(newaitems);
|
|
state1->nullbitmap = (bits8 *)
|
|
repalloc(state1->nullbitmap, (state1->aitems + 7) / 8);
|
|
}
|
|
array_bitmap_copy(state1->nullbitmap, state1->nitems,
|
|
state2->nullbitmap, 0,
|
|
state2->nitems);
|
|
}
|
|
|
|
memcpy(state1->data + state1->nbytes, state2->data, state2->nbytes);
|
|
state1->nbytes += state2->nbytes;
|
|
state1->nitems += state2->nitems;
|
|
|
|
state1->dims[0] += state2->dims[0];
|
|
/* remaining dims already match, per test above */
|
|
|
|
Assert(state1->array_type == state2->array_type);
|
|
Assert(state1->element_type == state2->element_type);
|
|
|
|
MemoryContextSwitchTo(oldContext);
|
|
}
|
|
|
|
PG_RETURN_POINTER(state1);
|
|
}
|
|
|
|
/*
|
|
* array_agg_array_serialize
|
|
* Serialize ArrayBuildStateArr into bytea.
|
|
*/
|
|
Datum
|
|
array_agg_array_serialize(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayBuildStateArr *state;
|
|
StringInfoData buf;
|
|
bytea *result;
|
|
|
|
/* cannot be called directly because of internal-type argument */
|
|
Assert(AggCheckCallContext(fcinfo, NULL));
|
|
|
|
state = (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
|
|
|
|
pq_begintypsend(&buf);
|
|
|
|
/*
|
|
* element_type. Putting this first is more convenient in deserialization
|
|
* so that we can init the new state sooner.
|
|
*/
|
|
pq_sendint32(&buf, state->element_type);
|
|
|
|
/* array_type */
|
|
pq_sendint32(&buf, state->array_type);
|
|
|
|
/* nbytes */
|
|
pq_sendint32(&buf, state->nbytes);
|
|
|
|
/* data */
|
|
pq_sendbytes(&buf, state->data, state->nbytes);
|
|
|
|
/* abytes */
|
|
pq_sendint32(&buf, state->abytes);
|
|
|
|
/* aitems */
|
|
pq_sendint32(&buf, state->aitems);
|
|
|
|
/* nullbitmap */
|
|
if (state->nullbitmap)
|
|
{
|
|
Assert(state->aitems > 0);
|
|
pq_sendbytes(&buf, state->nullbitmap, (state->aitems + 7) / 8);
|
|
}
|
|
|
|
/* nitems */
|
|
pq_sendint32(&buf, state->nitems);
|
|
|
|
/* ndims */
|
|
pq_sendint32(&buf, state->ndims);
|
|
|
|
/* dims: XXX should we just send ndims elements? */
|
|
pq_sendbytes(&buf, state->dims, sizeof(state->dims));
|
|
|
|
/* lbs */
|
|
pq_sendbytes(&buf, state->lbs, sizeof(state->lbs));
|
|
|
|
result = pq_endtypsend(&buf);
|
|
|
|
PG_RETURN_BYTEA_P(result);
|
|
}
|
|
|
|
Datum
|
|
array_agg_array_deserialize(PG_FUNCTION_ARGS)
|
|
{
|
|
bytea *sstate;
|
|
ArrayBuildStateArr *result;
|
|
StringInfoData buf;
|
|
Oid element_type;
|
|
Oid array_type;
|
|
int nbytes;
|
|
const char *temp;
|
|
|
|
/* cannot be called directly because of internal-type argument */
|
|
Assert(AggCheckCallContext(fcinfo, NULL));
|
|
|
|
sstate = PG_GETARG_BYTEA_PP(0);
|
|
|
|
/*
|
|
* Copy the bytea into a StringInfo so that we can "receive" it using the
|
|
* standard recv-function infrastructure.
|
|
*/
|
|
initStringInfo(&buf);
|
|
appendBinaryStringInfo(&buf,
|
|
VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate));
|
|
|
|
/* element_type */
|
|
element_type = pq_getmsgint(&buf, 4);
|
|
|
|
/* array_type */
|
|
array_type = pq_getmsgint(&buf, 4);
|
|
|
|
/* nbytes */
|
|
nbytes = pq_getmsgint(&buf, 4);
|
|
|
|
result = initArrayResultArr(array_type, element_type,
|
|
CurrentMemoryContext, false);
|
|
|
|
result->abytes = 1024;
|
|
while (result->abytes < nbytes)
|
|
result->abytes *= 2;
|
|
|
|
result->data = (char *) palloc(result->abytes);
|
|
|
|
/* data */
|
|
temp = pq_getmsgbytes(&buf, nbytes);
|
|
memcpy(result->data, temp, nbytes);
|
|
result->nbytes = nbytes;
|
|
|
|
/* abytes */
|
|
result->abytes = pq_getmsgint(&buf, 4);
|
|
|
|
/* aitems: might be 0 */
|
|
result->aitems = pq_getmsgint(&buf, 4);
|
|
|
|
/* nullbitmap */
|
|
if (result->aitems > 0)
|
|
{
|
|
int size = (result->aitems + 7) / 8;
|
|
|
|
result->nullbitmap = (bits8 *) palloc(size);
|
|
temp = pq_getmsgbytes(&buf, size);
|
|
memcpy(result->nullbitmap, temp, size);
|
|
}
|
|
else
|
|
result->nullbitmap = NULL;
|
|
|
|
/* nitems */
|
|
result->nitems = pq_getmsgint(&buf, 4);
|
|
|
|
/* ndims */
|
|
result->ndims = pq_getmsgint(&buf, 4);
|
|
|
|
/* dims */
|
|
temp = pq_getmsgbytes(&buf, sizeof(result->dims));
|
|
memcpy(result->dims, temp, sizeof(result->dims));
|
|
|
|
/* lbs */
|
|
temp = pq_getmsgbytes(&buf, sizeof(result->lbs));
|
|
memcpy(result->lbs, temp, sizeof(result->lbs));
|
|
|
|
pq_getmsgend(&buf);
|
|
pfree(buf.data);
|
|
|
|
PG_RETURN_POINTER(result);
|
|
}
|
|
|
|
Datum
|
|
array_agg_array_finalfn(PG_FUNCTION_ARGS)
|
|
{
|
|
Datum result;
|
|
ArrayBuildStateArr *state;
|
|
|
|
/* cannot be called directly because of internal-type argument */
|
|
Assert(AggCheckCallContext(fcinfo, NULL));
|
|
|
|
state = PG_ARGISNULL(0) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
|
|
|
|
if (state == NULL)
|
|
PG_RETURN_NULL(); /* returns null iff no input values */
|
|
|
|
/*
|
|
* Make the result. We cannot release the ArrayBuildStateArr because
|
|
* sometimes aggregate final functions are re-executed. Rather, it is
|
|
* nodeAgg.c's responsibility to reset the aggcontext when it's safe to do
|
|
* so.
|
|
*/
|
|
result = makeArrayResultArr(state, CurrentMemoryContext, false);
|
|
|
|
PG_RETURN_DATUM(result);
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
* array_position, array_position_start :
|
|
* return the offset of a value in an array.
|
|
*
|
|
* IS NOT DISTINCT FROM semantics are used for comparisons. Return NULL when
|
|
* the value is not found.
|
|
*-----------------------------------------------------------------------------
|
|
*/
|
|
Datum
|
|
array_position(PG_FUNCTION_ARGS)
|
|
{
|
|
return array_position_common(fcinfo);
|
|
}
|
|
|
|
Datum
|
|
array_position_start(PG_FUNCTION_ARGS)
|
|
{
|
|
return array_position_common(fcinfo);
|
|
}
|
|
|
|
/*
|
|
* array_position_common
|
|
* Common code for array_position and array_position_start
|
|
*
|
|
* These are separate wrappers for the sake of opr_sanity regression test.
|
|
* They are not strict so we have to test for null inputs explicitly.
|
|
*/
|
|
static Datum
|
|
array_position_common(FunctionCallInfo fcinfo)
|
|
{
|
|
ArrayType *array;
|
|
Oid collation = PG_GET_COLLATION();
|
|
Oid element_type;
|
|
Datum searched_element,
|
|
value;
|
|
bool isnull;
|
|
int position,
|
|
position_min;
|
|
bool found = false;
|
|
TypeCacheEntry *typentry;
|
|
ArrayMetaState *my_extra;
|
|
bool null_search;
|
|
ArrayIterator array_iterator;
|
|
|
|
if (PG_ARGISNULL(0))
|
|
PG_RETURN_NULL();
|
|
|
|
array = PG_GETARG_ARRAYTYPE_P(0);
|
|
|
|
/*
|
|
* We refuse to search for elements in multi-dimensional arrays, since we
|
|
* have no good way to report the element's location in the array.
|
|
*/
|
|
if (ARR_NDIM(array) > 1)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("searching for elements in multidimensional arrays is not supported")));
|
|
|
|
/* Searching in an empty array is well-defined, though: it always fails */
|
|
if (ARR_NDIM(array) < 1)
|
|
PG_RETURN_NULL();
|
|
|
|
if (PG_ARGISNULL(1))
|
|
{
|
|
/* fast return when the array doesn't have nulls */
|
|
if (!array_contains_nulls(array))
|
|
PG_RETURN_NULL();
|
|
searched_element = (Datum) 0;
|
|
null_search = true;
|
|
}
|
|
else
|
|
{
|
|
searched_element = PG_GETARG_DATUM(1);
|
|
null_search = false;
|
|
}
|
|
|
|
element_type = ARR_ELEMTYPE(array);
|
|
position = (ARR_LBOUND(array))[0] - 1;
|
|
|
|
/* figure out where to start */
|
|
if (PG_NARGS() == 3)
|
|
{
|
|
if (PG_ARGISNULL(2))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
errmsg("initial position must not be null")));
|
|
|
|
position_min = PG_GETARG_INT32(2);
|
|
}
|
|
else
|
|
position_min = (ARR_LBOUND(array))[0];
|
|
|
|
/*
|
|
* We arrange to look up type info for array_create_iterator only once per
|
|
* series of calls, assuming the element type doesn't change underneath
|
|
* us.
|
|
*/
|
|
my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
|
|
if (my_extra == NULL)
|
|
{
|
|
fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
|
|
sizeof(ArrayMetaState));
|
|
my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
|
|
my_extra->element_type = ~element_type;
|
|
}
|
|
|
|
if (my_extra->element_type != element_type)
|
|
{
|
|
get_typlenbyvalalign(element_type,
|
|
&my_extra->typlen,
|
|
&my_extra->typbyval,
|
|
&my_extra->typalign);
|
|
|
|
typentry = lookup_type_cache(element_type, TYPECACHE_EQ_OPR_FINFO);
|
|
|
|
if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_FUNCTION),
|
|
errmsg("could not identify an equality operator for type %s",
|
|
format_type_be(element_type))));
|
|
|
|
my_extra->element_type = element_type;
|
|
fmgr_info_cxt(typentry->eq_opr_finfo.fn_oid, &my_extra->proc,
|
|
fcinfo->flinfo->fn_mcxt);
|
|
}
|
|
|
|
/* Examine each array element until we find a match. */
|
|
array_iterator = array_create_iterator(array, 0, my_extra);
|
|
while (array_iterate(array_iterator, &value, &isnull))
|
|
{
|
|
position++;
|
|
|
|
/* skip initial elements if caller requested so */
|
|
if (position < position_min)
|
|
continue;
|
|
|
|
/*
|
|
* Can't look at the array element's value if it's null; but if we
|
|
* search for null, we have a hit and are done.
|
|
*/
|
|
if (isnull || null_search)
|
|
{
|
|
if (isnull && null_search)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
else
|
|
continue;
|
|
}
|
|
|
|
/* not nulls, so run the operator */
|
|
if (DatumGetBool(FunctionCall2Coll(&my_extra->proc, collation,
|
|
searched_element, value)))
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
array_free_iterator(array_iterator);
|
|
|
|
/* Avoid leaking memory when handed toasted input */
|
|
PG_FREE_IF_COPY(array, 0);
|
|
|
|
if (!found)
|
|
PG_RETURN_NULL();
|
|
|
|
PG_RETURN_INT32(position);
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
* array_positions :
|
|
* return an array of positions of a value in an array.
|
|
*
|
|
* IS NOT DISTINCT FROM semantics are used for comparisons. Returns NULL when
|
|
* the input array is NULL. When the value is not found in the array, returns
|
|
* an empty array.
|
|
*
|
|
* This is not strict so we have to test for null inputs explicitly.
|
|
*-----------------------------------------------------------------------------
|
|
*/
|
|
Datum
|
|
array_positions(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *array;
|
|
Oid collation = PG_GET_COLLATION();
|
|
Oid element_type;
|
|
Datum searched_element,
|
|
value;
|
|
bool isnull;
|
|
int position;
|
|
TypeCacheEntry *typentry;
|
|
ArrayMetaState *my_extra;
|
|
bool null_search;
|
|
ArrayIterator array_iterator;
|
|
ArrayBuildState *astate = NULL;
|
|
|
|
if (PG_ARGISNULL(0))
|
|
PG_RETURN_NULL();
|
|
|
|
array = PG_GETARG_ARRAYTYPE_P(0);
|
|
|
|
/*
|
|
* We refuse to search for elements in multi-dimensional arrays, since we
|
|
* have no good way to report the element's location in the array.
|
|
*/
|
|
if (ARR_NDIM(array) > 1)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("searching for elements in multidimensional arrays is not supported")));
|
|
|
|
astate = initArrayResult(INT4OID, CurrentMemoryContext, false);
|
|
|
|
/* Searching in an empty array is well-defined, though: it always fails */
|
|
if (ARR_NDIM(array) < 1)
|
|
PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext));
|
|
|
|
if (PG_ARGISNULL(1))
|
|
{
|
|
/* fast return when the array doesn't have nulls */
|
|
if (!array_contains_nulls(array))
|
|
PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext));
|
|
searched_element = (Datum) 0;
|
|
null_search = true;
|
|
}
|
|
else
|
|
{
|
|
searched_element = PG_GETARG_DATUM(1);
|
|
null_search = false;
|
|
}
|
|
|
|
element_type = ARR_ELEMTYPE(array);
|
|
position = (ARR_LBOUND(array))[0] - 1;
|
|
|
|
/*
|
|
* We arrange to look up type info for array_create_iterator only once per
|
|
* series of calls, assuming the element type doesn't change underneath
|
|
* us.
|
|
*/
|
|
my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
|
|
if (my_extra == NULL)
|
|
{
|
|
fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
|
|
sizeof(ArrayMetaState));
|
|
my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
|
|
my_extra->element_type = ~element_type;
|
|
}
|
|
|
|
if (my_extra->element_type != element_type)
|
|
{
|
|
get_typlenbyvalalign(element_type,
|
|
&my_extra->typlen,
|
|
&my_extra->typbyval,
|
|
&my_extra->typalign);
|
|
|
|
typentry = lookup_type_cache(element_type, TYPECACHE_EQ_OPR_FINFO);
|
|
|
|
if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_FUNCTION),
|
|
errmsg("could not identify an equality operator for type %s",
|
|
format_type_be(element_type))));
|
|
|
|
my_extra->element_type = element_type;
|
|
fmgr_info_cxt(typentry->eq_opr_finfo.fn_oid, &my_extra->proc,
|
|
fcinfo->flinfo->fn_mcxt);
|
|
}
|
|
|
|
/*
|
|
* Accumulate each array position iff the element matches the given
|
|
* element.
|
|
*/
|
|
array_iterator = array_create_iterator(array, 0, my_extra);
|
|
while (array_iterate(array_iterator, &value, &isnull))
|
|
{
|
|
position += 1;
|
|
|
|
/*
|
|
* Can't look at the array element's value if it's null; but if we
|
|
* search for null, we have a hit.
|
|
*/
|
|
if (isnull || null_search)
|
|
{
|
|
if (isnull && null_search)
|
|
astate =
|
|
accumArrayResult(astate, Int32GetDatum(position), false,
|
|
INT4OID, CurrentMemoryContext);
|
|
|
|
continue;
|
|
}
|
|
|
|
/* not nulls, so run the operator */
|
|
if (DatumGetBool(FunctionCall2Coll(&my_extra->proc, collation,
|
|
searched_element, value)))
|
|
astate =
|
|
accumArrayResult(astate, Int32GetDatum(position), false,
|
|
INT4OID, CurrentMemoryContext);
|
|
}
|
|
|
|
array_free_iterator(array_iterator);
|
|
|
|
/* Avoid leaking memory when handed toasted input */
|
|
PG_FREE_IF_COPY(array, 0);
|
|
|
|
PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext));
|
|
}
|
|
|
|
/*
|
|
* array_shuffle_n
|
|
* Return a copy of array with n randomly chosen items.
|
|
*
|
|
* The number of items must not exceed the size of the first dimension of the
|
|
* array. We preserve the first dimension's lower bound if keep_lb,
|
|
* else it's set to 1. Lower-order dimensions are preserved in any case.
|
|
*
|
|
* NOTE: it would be cleaner to look up the elmlen/elmbval/elmalign info
|
|
* from the system catalogs, given only the elmtyp. However, the caller is
|
|
* in a better position to cache this info across multiple calls.
|
|
*/
|
|
static ArrayType *
|
|
array_shuffle_n(ArrayType *array, int n, bool keep_lb,
|
|
Oid elmtyp, TypeCacheEntry *typentry)
|
|
{
|
|
ArrayType *result;
|
|
int ndim,
|
|
*dims,
|
|
*lbs,
|
|
nelm,
|
|
nitem,
|
|
rdims[MAXDIM],
|
|
rlbs[MAXDIM];
|
|
int16 elmlen;
|
|
bool elmbyval;
|
|
char elmalign;
|
|
Datum *elms,
|
|
*ielms;
|
|
bool *nuls,
|
|
*inuls;
|
|
|
|
ndim = ARR_NDIM(array);
|
|
dims = ARR_DIMS(array);
|
|
lbs = ARR_LBOUND(array);
|
|
|
|
elmlen = typentry->typlen;
|
|
elmbyval = typentry->typbyval;
|
|
elmalign = typentry->typalign;
|
|
|
|
/* If the target array is empty, exit fast */
|
|
if (ndim < 1 || dims[0] < 1 || n < 1)
|
|
return construct_empty_array(elmtyp);
|
|
|
|
deconstruct_array(array, elmtyp, elmlen, elmbyval, elmalign,
|
|
&elms, &nuls, &nelm);
|
|
|
|
nitem = dims[0]; /* total number of items */
|
|
nelm /= nitem; /* number of elements per item */
|
|
|
|
Assert(n <= nitem); /* else it's caller error */
|
|
|
|
/*
|
|
* Shuffle array using Fisher-Yates algorithm. Scan the array and swap
|
|
* current item (nelm datums starting at ielms) with a randomly chosen
|
|
* later item (nelm datums starting at jelms) in each iteration. We can
|
|
* stop once we've done n iterations; then first n items are the result.
|
|
*/
|
|
ielms = elms;
|
|
inuls = nuls;
|
|
for (int i = 0; i < n; i++)
|
|
{
|
|
int j = (int) pg_prng_uint64_range(&pg_global_prng_state, i, nitem - 1) * nelm;
|
|
Datum *jelms = elms + j;
|
|
bool *jnuls = nuls + j;
|
|
|
|
/* Swap i'th and j'th items; advance ielms/inuls to next item */
|
|
for (int k = 0; k < nelm; k++)
|
|
{
|
|
Datum elm = *ielms;
|
|
bool nul = *inuls;
|
|
|
|
*ielms++ = *jelms;
|
|
*inuls++ = *jnuls;
|
|
*jelms++ = elm;
|
|
*jnuls++ = nul;
|
|
}
|
|
}
|
|
|
|
/* Set up dimensions of the result */
|
|
memcpy(rdims, dims, ndim * sizeof(int));
|
|
memcpy(rlbs, lbs, ndim * sizeof(int));
|
|
rdims[0] = n;
|
|
if (!keep_lb)
|
|
rlbs[0] = 1;
|
|
|
|
result = construct_md_array(elms, nuls, ndim, rdims, rlbs,
|
|
elmtyp, elmlen, elmbyval, elmalign);
|
|
|
|
pfree(elms);
|
|
pfree(nuls);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* array_shuffle
|
|
*
|
|
* Returns an array with the same dimensions as the input array, with its
|
|
* first-dimension elements in random order.
|
|
*/
|
|
Datum
|
|
array_shuffle(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *array = PG_GETARG_ARRAYTYPE_P(0);
|
|
ArrayType *result;
|
|
Oid elmtyp;
|
|
TypeCacheEntry *typentry;
|
|
|
|
/*
|
|
* There is no point in shuffling empty arrays or arrays with less than
|
|
* two items.
|
|
*/
|
|
if (ARR_NDIM(array) < 1 || ARR_DIMS(array)[0] < 2)
|
|
PG_RETURN_ARRAYTYPE_P(array);
|
|
|
|
elmtyp = ARR_ELEMTYPE(array);
|
|
typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
|
|
if (typentry == NULL || typentry->type_id != elmtyp)
|
|
{
|
|
typentry = lookup_type_cache(elmtyp, 0);
|
|
fcinfo->flinfo->fn_extra = (void *) typentry;
|
|
}
|
|
|
|
result = array_shuffle_n(array, ARR_DIMS(array)[0], true, elmtyp, typentry);
|
|
|
|
PG_RETURN_ARRAYTYPE_P(result);
|
|
}
|
|
|
|
/*
|
|
* array_sample
|
|
*
|
|
* Returns an array of n randomly chosen first-dimension elements
|
|
* from the input array.
|
|
*/
|
|
Datum
|
|
array_sample(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *array = PG_GETARG_ARRAYTYPE_P(0);
|
|
int n = PG_GETARG_INT32(1);
|
|
ArrayType *result;
|
|
Oid elmtyp;
|
|
TypeCacheEntry *typentry;
|
|
int nitem;
|
|
|
|
nitem = (ARR_NDIM(array) < 1) ? 0 : ARR_DIMS(array)[0];
|
|
|
|
if (n < 0 || n > nitem)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("sample size must be between 0 and %d", nitem)));
|
|
|
|
elmtyp = ARR_ELEMTYPE(array);
|
|
typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
|
|
if (typentry == NULL || typentry->type_id != elmtyp)
|
|
{
|
|
typentry = lookup_type_cache(elmtyp, 0);
|
|
fcinfo->flinfo->fn_extra = (void *) typentry;
|
|
}
|
|
|
|
result = array_shuffle_n(array, n, false, elmtyp, typentry);
|
|
|
|
PG_RETURN_ARRAYTYPE_P(result);
|
|
}
|