postgresql/src/backend/utils/adt/array_userfuncs.c
Tom Lane f02b9085ad Prevent integer overflows in array subscripting calculations.
While we were (mostly) careful about ensuring that the dimensions of
arrays aren't large enough to cause integer overflow, the lower bound
values were generally not checked.  This allows situations where
lower_bound + dimension overflows an integer.  It seems that that's
harmless so far as array reading is concerned, except that array
elements with subscripts notionally exceeding INT_MAX are inaccessible.
However, it confuses various array-assignment logic, resulting in a
potential for memory stomps.

Fix by adding checks that array lower bounds aren't large enough to
cause lower_bound + dimension to overflow.  (Note: this results in
disallowing cases where the last subscript position would be exactly
INT_MAX.  In principle we could probably allow that, but there's a lot
of code that computes lower_bound + dimension and would need adjustment.
It seems doubtful that it's worth the trouble/risk to allow it.)

Somewhat independently of that, array_set_element() was careless
about possible overflow when checking the subscript of a fixed-length
array, creating a different route to memory stomps.  Fix that too.

Security: CVE-2021-32027
2021-05-10 10:44:38 -04:00

905 lines
24 KiB
C

/*-------------------------------------------------------------------------
*
* array_userfuncs.c
* Misc user-visible array support functions
*
* Copyright (c) 2003-2021, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/backend/utils/adt/array_userfuncs.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "catalog/pg_type.h"
#include "common/int.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"
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_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_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);
element_type = ARR_ELEMTYPE(array);
/*
* 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")));
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;
}
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);
element_type = ARR_ELEMTYPE(array);
position = (ARR_LBOUND(array))[0] - 1;
/*
* 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);
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;
}
/*
* 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));
}