summaryrefslogtreecommitdiffstats
path: root/src/backend/utils/adt/arrayfuncs.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/adt/arrayfuncs.c')
-rw-r--r--src/backend/utils/adt/arrayfuncs.c6722
1 files changed, 6722 insertions, 0 deletions
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
new file mode 100644
index 0000000..55ba9f7
--- /dev/null
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -0,0 +1,6722 @@
+/*-------------------------------------------------------------------------
+ *
+ * arrayfuncs.c
+ * Support functions for arrays.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/arrayfuncs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <ctype.h>
+#include <math.h>
+
+#include "access/htup_details.h"
+#include "catalog/pg_type.h"
+#include "funcapi.h"
+#include "libpq/pqformat.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/supportnodes.h"
+#include "optimizer/optimizer.h"
+#include "port/pg_bitutils.h"
+#include "utils/array.h"
+#include "utils/arrayaccess.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/selfuncs.h"
+#include "utils/typcache.h"
+
+
+/*
+ * GUC parameter
+ */
+bool Array_nulls = true;
+
+/*
+ * Local definitions
+ */
+#define ASSGN "="
+
+#define AARR_FREE_IF_COPY(array,n) \
+ do { \
+ if (!VARATT_IS_EXPANDED_HEADER(array)) \
+ PG_FREE_IF_COPY(array, n); \
+ } while (0)
+
+typedef enum
+{
+ ARRAY_NO_LEVEL,
+ ARRAY_LEVEL_STARTED,
+ ARRAY_ELEM_STARTED,
+ ARRAY_ELEM_COMPLETED,
+ ARRAY_QUOTED_ELEM_STARTED,
+ ARRAY_QUOTED_ELEM_COMPLETED,
+ ARRAY_ELEM_DELIMITED,
+ ARRAY_LEVEL_COMPLETED,
+ ARRAY_LEVEL_DELIMITED
+} ArrayParseState;
+
+/* Working state for array_iterate() */
+typedef struct ArrayIteratorData
+{
+ /* basic info about the array, set up during array_create_iterator() */
+ ArrayType *arr; /* array we're iterating through */
+ bits8 *nullbitmap; /* its null bitmap, if any */
+ int nitems; /* total number of elements in array */
+ int16 typlen; /* element type's length */
+ bool typbyval; /* element type's byval property */
+ char typalign; /* element type's align property */
+
+ /* information about the requested slice size */
+ int slice_ndim; /* slice dimension, or 0 if not slicing */
+ int slice_len; /* number of elements per slice */
+ int *slice_dims; /* slice dims array */
+ int *slice_lbound; /* slice lbound array */
+ Datum *slice_values; /* workspace of length slice_len */
+ bool *slice_nulls; /* workspace of length slice_len */
+
+ /* current position information, updated on each iteration */
+ char *data_ptr; /* our current position in the array */
+ int current_item; /* the item # we're at in the array */
+} ArrayIteratorData;
+
+static bool array_isspace(char ch);
+static int ArrayCount(const char *str, int *dim, char typdelim);
+static void ReadArrayStr(char *arrayStr, const char *origStr,
+ int nitems, int ndim, int *dim,
+ FmgrInfo *inputproc, Oid typioparam, int32 typmod,
+ char typdelim,
+ int typlen, bool typbyval, char typalign,
+ Datum *values, bool *nulls,
+ bool *hasnulls, int32 *nbytes);
+static void ReadArrayBinary(StringInfo buf, int nitems,
+ FmgrInfo *receiveproc, Oid typioparam, int32 typmod,
+ int typlen, bool typbyval, char typalign,
+ Datum *values, bool *nulls,
+ bool *hasnulls, int32 *nbytes);
+static Datum array_get_element_expanded(Datum arraydatum,
+ int nSubscripts, int *indx,
+ int arraytyplen,
+ int elmlen, bool elmbyval, char elmalign,
+ bool *isNull);
+static Datum array_set_element_expanded(Datum arraydatum,
+ int nSubscripts, int *indx,
+ Datum dataValue, bool isNull,
+ int arraytyplen,
+ int elmlen, bool elmbyval, char elmalign);
+static bool array_get_isnull(const bits8 *nullbitmap, int offset);
+static void array_set_isnull(bits8 *nullbitmap, int offset, bool isNull);
+static Datum ArrayCast(char *value, bool byval, int len);
+static int ArrayCastAndSet(Datum src,
+ int typlen, bool typbyval, char typalign,
+ char *dest);
+static char *array_seek(char *ptr, int offset, bits8 *nullbitmap, int nitems,
+ int typlen, bool typbyval, char typalign);
+static int array_nelems_size(char *ptr, int offset, bits8 *nullbitmap,
+ int nitems, int typlen, bool typbyval, char typalign);
+static int array_copy(char *destptr, int nitems,
+ char *srcptr, int offset, bits8 *nullbitmap,
+ int typlen, bool typbyval, char typalign);
+static int array_slice_size(char *arraydataptr, bits8 *arraynullsptr,
+ int ndim, int *dim, int *lb,
+ int *st, int *endp,
+ int typlen, bool typbyval, char typalign);
+static void array_extract_slice(ArrayType *newarray,
+ int ndim, int *dim, int *lb,
+ char *arraydataptr, bits8 *arraynullsptr,
+ int *st, int *endp,
+ int typlen, bool typbyval, char typalign);
+static void array_insert_slice(ArrayType *destArray, ArrayType *origArray,
+ ArrayType *srcArray,
+ int ndim, int *dim, int *lb,
+ int *st, int *endp,
+ int typlen, bool typbyval, char typalign);
+static int array_cmp(FunctionCallInfo fcinfo);
+static ArrayType *create_array_envelope(int ndims, int *dimv, int *lbsv, int nbytes,
+ Oid elmtype, int dataoffset);
+static ArrayType *array_fill_internal(ArrayType *dims, ArrayType *lbs,
+ Datum value, bool isnull, Oid elmtype,
+ FunctionCallInfo fcinfo);
+static ArrayType *array_replace_internal(ArrayType *array,
+ Datum search, bool search_isnull,
+ Datum replace, bool replace_isnull,
+ bool remove, Oid collation,
+ FunctionCallInfo fcinfo);
+static int width_bucket_array_float8(Datum operand, ArrayType *thresholds);
+static int width_bucket_array_fixed(Datum operand,
+ ArrayType *thresholds,
+ Oid collation,
+ TypeCacheEntry *typentry);
+static int width_bucket_array_variable(Datum operand,
+ ArrayType *thresholds,
+ Oid collation,
+ TypeCacheEntry *typentry);
+
+
+/*
+ * array_in :
+ * converts an array from the external format in "string" to
+ * its internal format.
+ *
+ * return value :
+ * the internal representation of the input array
+ */
+Datum
+array_in(PG_FUNCTION_ARGS)
+{
+ char *string = PG_GETARG_CSTRING(0); /* external form */
+ Oid element_type = PG_GETARG_OID(1); /* type of an array
+ * element */
+ int32 typmod = PG_GETARG_INT32(2); /* typmod for array elements */
+ int typlen;
+ bool typbyval;
+ char typalign;
+ char typdelim;
+ Oid typioparam;
+ char *string_save,
+ *p;
+ int i,
+ nitems;
+ Datum *dataPtr;
+ bool *nullsPtr;
+ bool hasnulls;
+ int32 nbytes;
+ int32 dataoffset;
+ ArrayType *retval;
+ int ndim,
+ dim[MAXDIM],
+ lBound[MAXDIM];
+ ArrayMetaState *my_extra;
+
+ /*
+ * We arrange to look up info about element type, including its input
+ * conversion proc, 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 info about element type, including its input conversion proc
+ */
+ get_type_io_data(element_type, IOFunc_input,
+ &my_extra->typlen, &my_extra->typbyval,
+ &my_extra->typalign, &my_extra->typdelim,
+ &my_extra->typioparam, &my_extra->typiofunc);
+ fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc,
+ fcinfo->flinfo->fn_mcxt);
+ my_extra->element_type = element_type;
+ }
+ typlen = my_extra->typlen;
+ typbyval = my_extra->typbyval;
+ typalign = my_extra->typalign;
+ typdelim = my_extra->typdelim;
+ typioparam = my_extra->typioparam;
+
+ /* Make a modifiable copy of the input */
+ string_save = pstrdup(string);
+
+ /*
+ * If the input string starts with dimension info, read and use that.
+ * Otherwise, we require the input to be in curly-brace style, and we
+ * prescan the input to determine dimensions.
+ *
+ * Dimension info takes the form of one or more [n] or [m:n] items. The
+ * outer loop iterates once per dimension item.
+ */
+ p = string_save;
+ ndim = 0;
+ for (;;)
+ {
+ char *q;
+ int ub;
+
+ /*
+ * Note: we currently allow whitespace between, but not within,
+ * dimension items.
+ */
+ while (array_isspace(*p))
+ p++;
+ if (*p != '[')
+ break; /* no more dimension items */
+ p++;
+ if (ndim >= MAXDIM)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
+ ndim + 1, MAXDIM)));
+
+ for (q = p; isdigit((unsigned char) *q) || (*q == '-') || (*q == '+'); q++)
+ /* skip */ ;
+ if (q == p) /* no digits? */
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"", string),
+ errdetail("\"[\" must introduce explicitly-specified array dimensions.")));
+
+ if (*q == ':')
+ {
+ /* [m:n] format */
+ *q = '\0';
+ lBound[ndim] = atoi(p);
+ p = q + 1;
+ for (q = p; isdigit((unsigned char) *q) || (*q == '-') || (*q == '+'); q++)
+ /* skip */ ;
+ if (q == p) /* no digits? */
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"", string),
+ errdetail("Missing array dimension value.")));
+ }
+ else
+ {
+ /* [n] format */
+ lBound[ndim] = 1;
+ }
+ if (*q != ']')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"", string),
+ errdetail("Missing \"%s\" after array dimensions.",
+ "]")));
+
+ *q = '\0';
+ ub = atoi(p);
+ p = q + 1;
+ if (ub < lBound[ndim])
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("upper bound cannot be less than lower bound")));
+
+ dim[ndim] = ub - lBound[ndim] + 1;
+ ndim++;
+ }
+
+ if (ndim == 0)
+ {
+ /* No array dimensions, so intuit dimensions from brace structure */
+ if (*p != '{')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"", string),
+ errdetail("Array value must start with \"{\" or dimension information.")));
+ ndim = ArrayCount(p, dim, typdelim);
+ for (i = 0; i < ndim; i++)
+ lBound[i] = 1;
+ }
+ else
+ {
+ int ndim_braces,
+ dim_braces[MAXDIM];
+
+ /* If array dimensions are given, expect '=' operator */
+ if (strncmp(p, ASSGN, strlen(ASSGN)) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"", string),
+ errdetail("Missing \"%s\" after array dimensions.",
+ ASSGN)));
+ p += strlen(ASSGN);
+ while (array_isspace(*p))
+ p++;
+
+ /*
+ * intuit dimensions from brace structure -- it better match what we
+ * were given
+ */
+ if (*p != '{')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"", string),
+ errdetail("Array contents must start with \"{\".")));
+ ndim_braces = ArrayCount(p, dim_braces, typdelim);
+ if (ndim_braces != ndim)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"", string),
+ errdetail("Specified array dimensions do not match array contents.")));
+ for (i = 0; i < ndim; ++i)
+ {
+ if (dim[i] != dim_braces[i])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"", string),
+ errdetail("Specified array dimensions do not match array contents.")));
+ }
+ }
+
+#ifdef ARRAYDEBUG
+ printf("array_in- ndim %d (", ndim);
+ for (i = 0; i < ndim; i++)
+ {
+ printf(" %d", dim[i]);
+ };
+ printf(") for %s\n", string);
+#endif
+
+ /* This checks for overflow of the array dimensions */
+ nitems = ArrayGetNItems(ndim, dim);
+ ArrayCheckBounds(ndim, dim, lBound);
+
+ /* Empty array? */
+ if (nitems == 0)
+ PG_RETURN_ARRAYTYPE_P(construct_empty_array(element_type));
+
+ dataPtr = (Datum *) palloc(nitems * sizeof(Datum));
+ nullsPtr = (bool *) palloc(nitems * sizeof(bool));
+ ReadArrayStr(p, string,
+ nitems, ndim, dim,
+ &my_extra->proc, typioparam, typmod,
+ typdelim,
+ typlen, typbyval, typalign,
+ dataPtr, nullsPtr,
+ &hasnulls, &nbytes);
+ if (hasnulls)
+ {
+ dataoffset = ARR_OVERHEAD_WITHNULLS(ndim, nitems);
+ nbytes += dataoffset;
+ }
+ else
+ {
+ dataoffset = 0; /* marker for no null bitmap */
+ nbytes += ARR_OVERHEAD_NONULLS(ndim);
+ }
+ retval = (ArrayType *) palloc0(nbytes);
+ SET_VARSIZE(retval, nbytes);
+ retval->ndim = ndim;
+ retval->dataoffset = dataoffset;
+
+ /*
+ * This comes from the array's pg_type.typelem (which points to the base
+ * data type's pg_type.oid) and stores system oids in user tables. This
+ * oid must be preserved by binary upgrades.
+ */
+ retval->elemtype = element_type;
+ memcpy(ARR_DIMS(retval), dim, ndim * sizeof(int));
+ memcpy(ARR_LBOUND(retval), lBound, ndim * sizeof(int));
+
+ CopyArrayEls(retval,
+ dataPtr, nullsPtr, nitems,
+ typlen, typbyval, typalign,
+ true);
+
+ pfree(dataPtr);
+ pfree(nullsPtr);
+ pfree(string_save);
+
+ PG_RETURN_ARRAYTYPE_P(retval);
+}
+
+/*
+ * array_isspace() --- a non-locale-dependent isspace()
+ *
+ * We used to use isspace() for parsing array values, but that has
+ * undesirable results: an array value might be silently interpreted
+ * differently depending on the locale setting. Now we just hard-wire
+ * the traditional ASCII definition of isspace().
+ */
+static bool
+array_isspace(char ch)
+{
+ if (ch == ' ' ||
+ ch == '\t' ||
+ ch == '\n' ||
+ ch == '\r' ||
+ ch == '\v' ||
+ ch == '\f')
+ return true;
+ return false;
+}
+
+/*
+ * ArrayCount
+ * Determines the dimensions for an array string.
+ *
+ * Returns number of dimensions as function result. The axis lengths are
+ * returned in dim[], which must be of size MAXDIM.
+ */
+static int
+ArrayCount(const char *str, int *dim, char typdelim)
+{
+ int nest_level = 0,
+ i;
+ int ndim = 1,
+ temp[MAXDIM],
+ nelems[MAXDIM],
+ nelems_last[MAXDIM];
+ bool in_quotes = false;
+ bool eoArray = false;
+ bool empty_array = true;
+ const char *ptr;
+ ArrayParseState parse_state = ARRAY_NO_LEVEL;
+
+ for (i = 0; i < MAXDIM; ++i)
+ {
+ temp[i] = dim[i] = nelems_last[i] = 0;
+ nelems[i] = 1;
+ }
+
+ ptr = str;
+ while (!eoArray)
+ {
+ bool itemdone = false;
+
+ while (!itemdone)
+ {
+ if (parse_state == ARRAY_ELEM_STARTED ||
+ parse_state == ARRAY_QUOTED_ELEM_STARTED)
+ empty_array = false;
+
+ switch (*ptr)
+ {
+ case '\0':
+ /* Signal a premature end of the string */
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"", str),
+ errdetail("Unexpected end of input.")));
+ break;
+ case '\\':
+
+ /*
+ * An escape must be after a level start, after an element
+ * start, or after an element delimiter. In any case we
+ * now must be past an element start.
+ */
+ if (parse_state != ARRAY_LEVEL_STARTED &&
+ parse_state != ARRAY_ELEM_STARTED &&
+ parse_state != ARRAY_QUOTED_ELEM_STARTED &&
+ parse_state != ARRAY_ELEM_DELIMITED)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"", str),
+ errdetail("Unexpected \"%c\" character.",
+ '\\')));
+ if (parse_state != ARRAY_QUOTED_ELEM_STARTED)
+ parse_state = ARRAY_ELEM_STARTED;
+ /* skip the escaped character */
+ if (*(ptr + 1))
+ ptr++;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"", str),
+ errdetail("Unexpected end of input.")));
+ break;
+ case '"':
+
+ /*
+ * A quote must be after a level start, after a quoted
+ * element start, or after an element delimiter. In any
+ * case we now must be past an element start.
+ */
+ if (parse_state != ARRAY_LEVEL_STARTED &&
+ parse_state != ARRAY_QUOTED_ELEM_STARTED &&
+ parse_state != ARRAY_ELEM_DELIMITED)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"", str),
+ errdetail("Unexpected array element.")));
+ in_quotes = !in_quotes;
+ if (in_quotes)
+ parse_state = ARRAY_QUOTED_ELEM_STARTED;
+ else
+ parse_state = ARRAY_QUOTED_ELEM_COMPLETED;
+ break;
+ case '{':
+ if (!in_quotes)
+ {
+ /*
+ * A left brace can occur if no nesting has occurred
+ * yet, after a level start, or after a level
+ * delimiter.
+ */
+ if (parse_state != ARRAY_NO_LEVEL &&
+ parse_state != ARRAY_LEVEL_STARTED &&
+ parse_state != ARRAY_LEVEL_DELIMITED)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"", str),
+ errdetail("Unexpected \"%c\" character.",
+ '{')));
+ parse_state = ARRAY_LEVEL_STARTED;
+ if (nest_level >= MAXDIM)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
+ nest_level + 1, MAXDIM)));
+ temp[nest_level] = 0;
+ nest_level++;
+ if (ndim < nest_level)
+ ndim = nest_level;
+ }
+ break;
+ case '}':
+ if (!in_quotes)
+ {
+ /*
+ * A right brace can occur after an element start, an
+ * element completion, a quoted element completion, or
+ * a level completion.
+ */
+ if (parse_state != ARRAY_ELEM_STARTED &&
+ parse_state != ARRAY_ELEM_COMPLETED &&
+ parse_state != ARRAY_QUOTED_ELEM_COMPLETED &&
+ parse_state != ARRAY_LEVEL_COMPLETED &&
+ !(nest_level == 1 && parse_state == ARRAY_LEVEL_STARTED))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"", str),
+ errdetail("Unexpected \"%c\" character.",
+ '}')));
+ parse_state = ARRAY_LEVEL_COMPLETED;
+ if (nest_level == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"", str),
+ errdetail("Unmatched \"%c\" character.", '}')));
+ nest_level--;
+
+ if (nelems_last[nest_level] != 0 &&
+ nelems[nest_level] != nelems_last[nest_level])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"", str),
+ errdetail("Multidimensional arrays must have "
+ "sub-arrays with matching "
+ "dimensions.")));
+ nelems_last[nest_level] = nelems[nest_level];
+ nelems[nest_level] = 1;
+ if (nest_level == 0)
+ eoArray = itemdone = true;
+ else
+ {
+ /*
+ * We don't set itemdone here; see comments in
+ * ReadArrayStr
+ */
+ temp[nest_level - 1]++;
+ }
+ }
+ break;
+ default:
+ if (!in_quotes)
+ {
+ if (*ptr == typdelim)
+ {
+ /*
+ * Delimiters can occur after an element start, an
+ * element completion, a quoted element
+ * completion, or a level completion.
+ */
+ if (parse_state != ARRAY_ELEM_STARTED &&
+ parse_state != ARRAY_ELEM_COMPLETED &&
+ parse_state != ARRAY_QUOTED_ELEM_COMPLETED &&
+ parse_state != ARRAY_LEVEL_COMPLETED)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"", str),
+ errdetail("Unexpected \"%c\" character.",
+ typdelim)));
+ if (parse_state == ARRAY_LEVEL_COMPLETED)
+ parse_state = ARRAY_LEVEL_DELIMITED;
+ else
+ parse_state = ARRAY_ELEM_DELIMITED;
+ itemdone = true;
+ nelems[nest_level - 1]++;
+ }
+ else if (!array_isspace(*ptr))
+ {
+ /*
+ * Other non-space characters must be after a
+ * level start, after an element start, or after
+ * an element delimiter. In any case we now must
+ * be past an element start.
+ */
+ if (parse_state != ARRAY_LEVEL_STARTED &&
+ parse_state != ARRAY_ELEM_STARTED &&
+ parse_state != ARRAY_ELEM_DELIMITED)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"", str),
+ errdetail("Unexpected array element.")));
+ parse_state = ARRAY_ELEM_STARTED;
+ }
+ }
+ break;
+ }
+ if (!itemdone)
+ ptr++;
+ }
+ temp[ndim - 1]++;
+ ptr++;
+ }
+
+ /* only whitespace is allowed after the closing brace */
+ while (*ptr)
+ {
+ if (!array_isspace(*ptr++))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"", str),
+ errdetail("Junk after closing right brace.")));
+ }
+
+ /* special case for an empty array */
+ if (empty_array)
+ return 0;
+
+ for (i = 0; i < ndim; ++i)
+ dim[i] = temp[i];
+
+ return ndim;
+}
+
+/*
+ * ReadArrayStr :
+ * parses the array string pointed to by "arrayStr" and converts the values
+ * to internal format. Unspecified elements are initialized to nulls.
+ * The array dimensions must already have been determined.
+ *
+ * Inputs:
+ * arrayStr: the string to parse.
+ * CAUTION: the contents of "arrayStr" will be modified!
+ * origStr: the unmodified input string, used only in error messages.
+ * nitems: total number of array elements, as already determined.
+ * ndim: number of array dimensions
+ * dim[]: array axis lengths
+ * inputproc: type-specific input procedure for element datatype.
+ * typioparam, typmod: auxiliary values to pass to inputproc.
+ * typdelim: the value delimiter (type-specific).
+ * typlen, typbyval, typalign: storage parameters of element datatype.
+ *
+ * Outputs:
+ * values[]: filled with converted data values.
+ * nulls[]: filled with is-null markers.
+ * *hasnulls: set true iff there are any null elements.
+ * *nbytes: set to total size of data area needed (including alignment
+ * padding but not including array header overhead).
+ *
+ * Note that values[] and nulls[] are allocated by the caller, and must have
+ * nitems elements.
+ */
+static void
+ReadArrayStr(char *arrayStr,
+ const char *origStr,
+ int nitems,
+ int ndim,
+ int *dim,
+ FmgrInfo *inputproc,
+ Oid typioparam,
+ int32 typmod,
+ char typdelim,
+ int typlen,
+ bool typbyval,
+ char typalign,
+ Datum *values,
+ bool *nulls,
+ bool *hasnulls,
+ int32 *nbytes)
+{
+ int i,
+ nest_level = 0;
+ char *srcptr;
+ bool in_quotes = false;
+ bool eoArray = false;
+ bool hasnull;
+ int32 totbytes;
+ int indx[MAXDIM],
+ prod[MAXDIM];
+
+ mda_get_prod(ndim, dim, prod);
+ MemSet(indx, 0, sizeof(indx));
+
+ /* Initialize is-null markers to true */
+ memset(nulls, true, nitems * sizeof(bool));
+
+ /*
+ * We have to remove " and \ characters to create a clean item value to
+ * pass to the datatype input routine. We overwrite each item value
+ * in-place within arrayStr to do this. srcptr is the current scan point,
+ * and dstptr is where we are copying to.
+ *
+ * We also want to suppress leading and trailing unquoted whitespace. We
+ * use the leadingspace flag to suppress leading space. Trailing space is
+ * tracked by using dstendptr to point to the last significant output
+ * character.
+ *
+ * The error checking in this routine is mostly pro-forma, since we expect
+ * that ArrayCount() already validated the string. So we don't bother
+ * with errdetail messages.
+ */
+ srcptr = arrayStr;
+ while (!eoArray)
+ {
+ bool itemdone = false;
+ bool leadingspace = true;
+ bool hasquoting = false;
+ char *itemstart;
+ char *dstptr;
+ char *dstendptr;
+
+ i = -1;
+ itemstart = dstptr = dstendptr = srcptr;
+
+ while (!itemdone)
+ {
+ switch (*srcptr)
+ {
+ case '\0':
+ /* Signal a premature end of the string */
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"",
+ origStr)));
+ break;
+ case '\\':
+ /* Skip backslash, copy next character as-is. */
+ srcptr++;
+ if (*srcptr == '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"",
+ origStr)));
+ *dstptr++ = *srcptr++;
+ /* Treat the escaped character as non-whitespace */
+ leadingspace = false;
+ dstendptr = dstptr;
+ hasquoting = true; /* can't be a NULL marker */
+ break;
+ case '"':
+ in_quotes = !in_quotes;
+ if (in_quotes)
+ leadingspace = false;
+ else
+ {
+ /*
+ * Advance dstendptr when we exit in_quotes; this
+ * saves having to do it in all the other in_quotes
+ * cases.
+ */
+ dstendptr = dstptr;
+ }
+ hasquoting = true; /* can't be a NULL marker */
+ srcptr++;
+ break;
+ case '{':
+ if (!in_quotes)
+ {
+ if (nest_level >= ndim)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"",
+ origStr)));
+ nest_level++;
+ indx[nest_level - 1] = 0;
+ srcptr++;
+ }
+ else
+ *dstptr++ = *srcptr++;
+ break;
+ case '}':
+ if (!in_quotes)
+ {
+ if (nest_level == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"",
+ origStr)));
+ if (i == -1)
+ i = ArrayGetOffset0(ndim, indx, prod);
+ indx[nest_level - 1] = 0;
+ nest_level--;
+ if (nest_level == 0)
+ eoArray = itemdone = true;
+ else
+ indx[nest_level - 1]++;
+ srcptr++;
+ }
+ else
+ *dstptr++ = *srcptr++;
+ break;
+ default:
+ if (in_quotes)
+ *dstptr++ = *srcptr++;
+ else if (*srcptr == typdelim)
+ {
+ if (i == -1)
+ i = ArrayGetOffset0(ndim, indx, prod);
+ itemdone = true;
+ indx[ndim - 1]++;
+ srcptr++;
+ }
+ else if (array_isspace(*srcptr))
+ {
+ /*
+ * If leading space, drop it immediately. Else, copy
+ * but don't advance dstendptr.
+ */
+ if (leadingspace)
+ srcptr++;
+ else
+ *dstptr++ = *srcptr++;
+ }
+ else
+ {
+ *dstptr++ = *srcptr++;
+ leadingspace = false;
+ dstendptr = dstptr;
+ }
+ break;
+ }
+ }
+
+ Assert(dstptr < srcptr);
+ *dstendptr = '\0';
+
+ if (i < 0 || i >= nitems)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed array literal: \"%s\"",
+ origStr)));
+
+ if (Array_nulls && !hasquoting &&
+ pg_strcasecmp(itemstart, "NULL") == 0)
+ {
+ /* it's a NULL item */
+ values[i] = InputFunctionCall(inputproc, NULL,
+ typioparam, typmod);
+ nulls[i] = true;
+ }
+ else
+ {
+ values[i] = InputFunctionCall(inputproc, itemstart,
+ typioparam, typmod);
+ nulls[i] = false;
+ }
+ }
+
+ /*
+ * Check for nulls, compute total data space needed
+ */
+ hasnull = false;
+ totbytes = 0;
+ for (i = 0; i < nitems; i++)
+ {
+ if (nulls[i])
+ hasnull = true;
+ else
+ {
+ /* let's just make sure data is not toasted */
+ if (typlen == -1)
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM(values[i]));
+ totbytes = att_addlength_datum(totbytes, typlen, values[i]);
+ totbytes = att_align_nominal(totbytes, typalign);
+ /* check for overflow of total request */
+ if (!AllocSizeIsValid(totbytes))
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("array size exceeds the maximum allowed (%d)",
+ (int) MaxAllocSize)));
+ }
+ }
+ *hasnulls = hasnull;
+ *nbytes = totbytes;
+}
+
+
+/*
+ * Copy data into an array object from a temporary array of Datums.
+ *
+ * array: array object (with header fields already filled in)
+ * values: array of Datums to be copied
+ * nulls: array of is-null flags (can be NULL if no nulls)
+ * nitems: number of Datums to be copied
+ * typbyval, typlen, typalign: info about element datatype
+ * freedata: if true and element type is pass-by-ref, pfree data values
+ * referenced by Datums after copying them.
+ *
+ * If the input data is of varlena type, the caller must have ensured that
+ * the values are not toasted. (Doing it here doesn't work since the
+ * caller has already allocated space for the array...)
+ */
+void
+CopyArrayEls(ArrayType *array,
+ Datum *values,
+ bool *nulls,
+ int nitems,
+ int typlen,
+ bool typbyval,
+ char typalign,
+ bool freedata)
+{
+ char *p = ARR_DATA_PTR(array);
+ bits8 *bitmap = ARR_NULLBITMAP(array);
+ int bitval = 0;
+ int bitmask = 1;
+ int i;
+
+ if (typbyval)
+ freedata = false;
+
+ for (i = 0; i < nitems; i++)
+ {
+ if (nulls && nulls[i])
+ {
+ if (!bitmap) /* shouldn't happen */
+ elog(ERROR, "null array element where not supported");
+ /* bitmap bit stays 0 */
+ }
+ else
+ {
+ bitval |= bitmask;
+ p += ArrayCastAndSet(values[i], typlen, typbyval, typalign, p);
+ if (freedata)
+ pfree(DatumGetPointer(values[i]));
+ }
+ if (bitmap)
+ {
+ bitmask <<= 1;
+ if (bitmask == 0x100)
+ {
+ *bitmap++ = bitval;
+ bitval = 0;
+ bitmask = 1;
+ }
+ }
+ }
+
+ if (bitmap && bitmask != 1)
+ *bitmap = bitval;
+}
+
+/*
+ * array_out :
+ * takes the internal representation of an array and returns a string
+ * containing the array in its external format.
+ */
+Datum
+array_out(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *v = PG_GETARG_ANY_ARRAY_P(0);
+ Oid element_type = AARR_ELEMTYPE(v);
+ int typlen;
+ bool typbyval;
+ char typalign;
+ char typdelim;
+ char *p,
+ *tmp,
+ *retval,
+ **values,
+ dims_str[(MAXDIM * 33) + 2];
+
+ /*
+ * 33 per dim since we assume 15 digits per number + ':' +'[]'
+ *
+ * +2 allows for assignment operator + trailing null
+ */
+ bool *needquotes,
+ needdims = false;
+ size_t overall_length;
+ int nitems,
+ i,
+ j,
+ k,
+ indx[MAXDIM];
+ int ndim,
+ *dims,
+ *lb;
+ array_iter iter;
+ ArrayMetaState *my_extra;
+
+ /*
+ * We arrange to look up info about element type, including its output
+ * conversion proc, 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 info about element type, including its output conversion proc
+ */
+ get_type_io_data(element_type, IOFunc_output,
+ &my_extra->typlen, &my_extra->typbyval,
+ &my_extra->typalign, &my_extra->typdelim,
+ &my_extra->typioparam, &my_extra->typiofunc);
+ fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc,
+ fcinfo->flinfo->fn_mcxt);
+ my_extra->element_type = element_type;
+ }
+ typlen = my_extra->typlen;
+ typbyval = my_extra->typbyval;
+ typalign = my_extra->typalign;
+ typdelim = my_extra->typdelim;
+
+ ndim = AARR_NDIM(v);
+ dims = AARR_DIMS(v);
+ lb = AARR_LBOUND(v);
+ nitems = ArrayGetNItems(ndim, dims);
+
+ if (nitems == 0)
+ {
+ retval = pstrdup("{}");
+ PG_RETURN_CSTRING(retval);
+ }
+
+ /*
+ * we will need to add explicit dimensions if any dimension has a lower
+ * bound other than one
+ */
+ for (i = 0; i < ndim; i++)
+ {
+ if (lb[i] != 1)
+ {
+ needdims = true;
+ break;
+ }
+ }
+
+ /*
+ * Convert all values to string form, count total space needed (including
+ * any overhead such as escaping backslashes), and detect whether each
+ * item needs double quotes.
+ */
+ values = (char **) palloc(nitems * sizeof(char *));
+ needquotes = (bool *) palloc(nitems * sizeof(bool));
+ overall_length = 0;
+
+ array_iter_setup(&iter, v);
+
+ for (i = 0; i < nitems; i++)
+ {
+ Datum itemvalue;
+ bool isnull;
+ bool needquote;
+
+ /* Get source element, checking for NULL */
+ itemvalue = array_iter_next(&iter, &isnull, i,
+ typlen, typbyval, typalign);
+
+ if (isnull)
+ {
+ values[i] = pstrdup("NULL");
+ overall_length += 4;
+ needquote = false;
+ }
+ else
+ {
+ values[i] = OutputFunctionCall(&my_extra->proc, itemvalue);
+
+ /* count data plus backslashes; detect chars needing quotes */
+ if (values[i][0] == '\0')
+ needquote = true; /* force quotes for empty string */
+ else if (pg_strcasecmp(values[i], "NULL") == 0)
+ needquote = true; /* force quotes for literal NULL */
+ else
+ needquote = false;
+
+ for (tmp = values[i]; *tmp != '\0'; tmp++)
+ {
+ char ch = *tmp;
+
+ overall_length += 1;
+ if (ch == '"' || ch == '\\')
+ {
+ needquote = true;
+ overall_length += 1;
+ }
+ else if (ch == '{' || ch == '}' || ch == typdelim ||
+ array_isspace(ch))
+ needquote = true;
+ }
+ }
+
+ needquotes[i] = needquote;
+
+ /* Count the pair of double quotes, if needed */
+ if (needquote)
+ overall_length += 2;
+ /* and the comma (or other typdelim delimiter) */
+ overall_length += 1;
+ }
+
+ /*
+ * The very last array element doesn't have a typdelim delimiter after it,
+ * but that's OK; that space is needed for the trailing '\0'.
+ *
+ * Now count total number of curly brace pairs in output string.
+ */
+ for (i = j = 0, k = 1; i < ndim; i++)
+ {
+ j += k, k *= dims[i];
+ }
+ overall_length += 2 * j;
+
+ /* Format explicit dimensions if required */
+ dims_str[0] = '\0';
+ if (needdims)
+ {
+ char *ptr = dims_str;
+
+ for (i = 0; i < ndim; i++)
+ {
+ sprintf(ptr, "[%d:%d]", lb[i], lb[i] + dims[i] - 1);
+ ptr += strlen(ptr);
+ }
+ *ptr++ = *ASSGN;
+ *ptr = '\0';
+ overall_length += ptr - dims_str;
+ }
+
+ /* Now construct the output string */
+ retval = (char *) palloc(overall_length);
+ p = retval;
+
+#define APPENDSTR(str) (strcpy(p, (str)), p += strlen(p))
+#define APPENDCHAR(ch) (*p++ = (ch), *p = '\0')
+
+ if (needdims)
+ APPENDSTR(dims_str);
+ APPENDCHAR('{');
+ for (i = 0; i < ndim; i++)
+ indx[i] = 0;
+ j = 0;
+ k = 0;
+ do
+ {
+ for (i = j; i < ndim - 1; i++)
+ APPENDCHAR('{');
+
+ if (needquotes[k])
+ {
+ APPENDCHAR('"');
+ for (tmp = values[k]; *tmp; tmp++)
+ {
+ char ch = *tmp;
+
+ if (ch == '"' || ch == '\\')
+ *p++ = '\\';
+ *p++ = ch;
+ }
+ *p = '\0';
+ APPENDCHAR('"');
+ }
+ else
+ APPENDSTR(values[k]);
+ pfree(values[k++]);
+
+ for (i = ndim - 1; i >= 0; i--)
+ {
+ if (++(indx[i]) < dims[i])
+ {
+ APPENDCHAR(typdelim);
+ break;
+ }
+ else
+ {
+ indx[i] = 0;
+ APPENDCHAR('}');
+ }
+ }
+ j = i;
+ } while (j != -1);
+
+#undef APPENDSTR
+#undef APPENDCHAR
+
+ /* Assert that we calculated the string length accurately */
+ Assert(overall_length == (p - retval + 1));
+
+ pfree(values);
+ pfree(needquotes);
+
+ PG_RETURN_CSTRING(retval);
+}
+
+/*
+ * array_recv :
+ * converts an array from the external binary format to
+ * its internal format.
+ *
+ * return value :
+ * the internal representation of the input array
+ */
+Datum
+array_recv(PG_FUNCTION_ARGS)
+{
+ StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
+ Oid spec_element_type = PG_GETARG_OID(1); /* type of an array
+ * element */
+ int32 typmod = PG_GETARG_INT32(2); /* typmod for array elements */
+ Oid element_type;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ Oid typioparam;
+ int i,
+ nitems;
+ Datum *dataPtr;
+ bool *nullsPtr;
+ bool hasnulls;
+ int32 nbytes;
+ int32 dataoffset;
+ ArrayType *retval;
+ int ndim,
+ flags,
+ dim[MAXDIM],
+ lBound[MAXDIM];
+ ArrayMetaState *my_extra;
+
+ /* Get the array header information */
+ ndim = pq_getmsgint(buf, 4);
+ if (ndim < 0) /* we do allow zero-dimension arrays */
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+ errmsg("invalid number of dimensions: %d", ndim)));
+ if (ndim > MAXDIM)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
+ ndim, MAXDIM)));
+
+ flags = pq_getmsgint(buf, 4);
+ if (flags != 0 && flags != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+ errmsg("invalid array flags")));
+
+ /* Check element type recorded in the data */
+ element_type = pq_getmsgint(buf, sizeof(Oid));
+
+ /*
+ * From a security standpoint, it doesn't matter whether the input's
+ * element type matches what we expect: the element type's receive
+ * function has to be robust enough to cope with invalid data. However,
+ * from a user-friendliness standpoint, it's nicer to complain about type
+ * mismatches than to throw "improper binary format" errors. But there's
+ * a problem: only built-in types have OIDs that are stable enough to
+ * believe that a mismatch is a real issue. So complain only if both OIDs
+ * are in the built-in range. Otherwise, carry on with the element type
+ * we "should" be getting.
+ */
+ if (element_type != spec_element_type)
+ {
+ if (element_type < FirstGenbkiObjectId &&
+ spec_element_type < FirstGenbkiObjectId)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("binary data has array element type %u (%s) instead of expected %u (%s)",
+ element_type,
+ format_type_extended(element_type, -1,
+ FORMAT_TYPE_ALLOW_INVALID),
+ spec_element_type,
+ format_type_extended(spec_element_type, -1,
+ FORMAT_TYPE_ALLOW_INVALID))));
+ element_type = spec_element_type;
+ }
+
+ for (i = 0; i < ndim; i++)
+ {
+ dim[i] = pq_getmsgint(buf, 4);
+ lBound[i] = pq_getmsgint(buf, 4);
+ }
+
+ /* This checks for overflow of array dimensions */
+ nitems = ArrayGetNItems(ndim, dim);
+ ArrayCheckBounds(ndim, dim, lBound);
+
+ /*
+ * We arrange to look up info about element type, including its receive
+ * conversion proc, 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 info about element type, including its receive proc */
+ get_type_io_data(element_type, IOFunc_receive,
+ &my_extra->typlen, &my_extra->typbyval,
+ &my_extra->typalign, &my_extra->typdelim,
+ &my_extra->typioparam, &my_extra->typiofunc);
+ if (!OidIsValid(my_extra->typiofunc))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("no binary input function available for type %s",
+ format_type_be(element_type))));
+ fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc,
+ fcinfo->flinfo->fn_mcxt);
+ my_extra->element_type = element_type;
+ }
+
+ if (nitems == 0)
+ {
+ /* Return empty array ... but not till we've validated element_type */
+ PG_RETURN_ARRAYTYPE_P(construct_empty_array(element_type));
+ }
+
+ typlen = my_extra->typlen;
+ typbyval = my_extra->typbyval;
+ typalign = my_extra->typalign;
+ typioparam = my_extra->typioparam;
+
+ dataPtr = (Datum *) palloc(nitems * sizeof(Datum));
+ nullsPtr = (bool *) palloc(nitems * sizeof(bool));
+ ReadArrayBinary(buf, nitems,
+ &my_extra->proc, typioparam, typmod,
+ typlen, typbyval, typalign,
+ dataPtr, nullsPtr,
+ &hasnulls, &nbytes);
+ if (hasnulls)
+ {
+ dataoffset = ARR_OVERHEAD_WITHNULLS(ndim, nitems);
+ nbytes += dataoffset;
+ }
+ else
+ {
+ dataoffset = 0; /* marker for no null bitmap */
+ nbytes += ARR_OVERHEAD_NONULLS(ndim);
+ }
+ retval = (ArrayType *) palloc0(nbytes);
+ SET_VARSIZE(retval, nbytes);
+ retval->ndim = ndim;
+ retval->dataoffset = dataoffset;
+ retval->elemtype = element_type;
+ memcpy(ARR_DIMS(retval), dim, ndim * sizeof(int));
+ memcpy(ARR_LBOUND(retval), lBound, ndim * sizeof(int));
+
+ CopyArrayEls(retval,
+ dataPtr, nullsPtr, nitems,
+ typlen, typbyval, typalign,
+ true);
+
+ pfree(dataPtr);
+ pfree(nullsPtr);
+
+ PG_RETURN_ARRAYTYPE_P(retval);
+}
+
+/*
+ * ReadArrayBinary:
+ * collect the data elements of an array being read in binary style.
+ *
+ * Inputs:
+ * buf: the data buffer to read from.
+ * nitems: total number of array elements (already read).
+ * receiveproc: type-specific receive procedure for element datatype.
+ * typioparam, typmod: auxiliary values to pass to receiveproc.
+ * typlen, typbyval, typalign: storage parameters of element datatype.
+ *
+ * Outputs:
+ * values[]: filled with converted data values.
+ * nulls[]: filled with is-null markers.
+ * *hasnulls: set true iff there are any null elements.
+ * *nbytes: set to total size of data area needed (including alignment
+ * padding but not including array header overhead).
+ *
+ * Note that values[] and nulls[] are allocated by the caller, and must have
+ * nitems elements.
+ */
+static void
+ReadArrayBinary(StringInfo buf,
+ int nitems,
+ FmgrInfo *receiveproc,
+ Oid typioparam,
+ int32 typmod,
+ int typlen,
+ bool typbyval,
+ char typalign,
+ Datum *values,
+ bool *nulls,
+ bool *hasnulls,
+ int32 *nbytes)
+{
+ int i;
+ bool hasnull;
+ int32 totbytes;
+
+ for (i = 0; i < nitems; i++)
+ {
+ int itemlen;
+ StringInfoData elem_buf;
+ char csave;
+
+ /* Get and check the item length */
+ itemlen = pq_getmsgint(buf, 4);
+ if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+ errmsg("insufficient data left in message")));
+
+ if (itemlen == -1)
+ {
+ /* -1 length means NULL */
+ values[i] = ReceiveFunctionCall(receiveproc, NULL,
+ typioparam, typmod);
+ nulls[i] = true;
+ continue;
+ }
+
+ /*
+ * 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 */
+ values[i] = ReceiveFunctionCall(receiveproc, &elem_buf,
+ typioparam, typmod);
+ nulls[i] = false;
+
+ /* Trouble if it didn't eat the whole buffer */
+ if (elem_buf.cursor != itemlen)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+ errmsg("improper binary format in array element %d",
+ i + 1)));
+
+ buf->data[buf->cursor] = csave;
+ }
+
+ /*
+ * Check for nulls, compute total data space needed
+ */
+ hasnull = false;
+ totbytes = 0;
+ for (i = 0; i < nitems; i++)
+ {
+ if (nulls[i])
+ hasnull = true;
+ else
+ {
+ /* let's just make sure data is not toasted */
+ if (typlen == -1)
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM(values[i]));
+ totbytes = att_addlength_datum(totbytes, typlen, values[i]);
+ totbytes = att_align_nominal(totbytes, typalign);
+ /* check for overflow of total request */
+ if (!AllocSizeIsValid(totbytes))
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("array size exceeds the maximum allowed (%d)",
+ (int) MaxAllocSize)));
+ }
+ }
+ *hasnulls = hasnull;
+ *nbytes = totbytes;
+}
+
+
+/*
+ * array_send :
+ * takes the internal representation of an array and returns a bytea
+ * containing the array in its external binary format.
+ */
+Datum
+array_send(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *v = PG_GETARG_ANY_ARRAY_P(0);
+ Oid element_type = AARR_ELEMTYPE(v);
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int nitems,
+ i;
+ int ndim,
+ *dim,
+ *lb;
+ StringInfoData buf;
+ array_iter iter;
+ ArrayMetaState *my_extra;
+
+ /*
+ * We arrange to look up info about element type, including its send
+ * conversion proc, 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 info about element type, including its send proc */
+ get_type_io_data(element_type, IOFunc_send,
+ &my_extra->typlen, &my_extra->typbyval,
+ &my_extra->typalign, &my_extra->typdelim,
+ &my_extra->typioparam, &my_extra->typiofunc);
+ if (!OidIsValid(my_extra->typiofunc))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("no binary output function available for type %s",
+ format_type_be(element_type))));
+ fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc,
+ fcinfo->flinfo->fn_mcxt);
+ my_extra->element_type = element_type;
+ }
+ typlen = my_extra->typlen;
+ typbyval = my_extra->typbyval;
+ typalign = my_extra->typalign;
+
+ ndim = AARR_NDIM(v);
+ dim = AARR_DIMS(v);
+ lb = AARR_LBOUND(v);
+ nitems = ArrayGetNItems(ndim, dim);
+
+ pq_begintypsend(&buf);
+
+ /* Send the array header information */
+ pq_sendint32(&buf, ndim);
+ pq_sendint32(&buf, AARR_HASNULL(v) ? 1 : 0);
+ pq_sendint32(&buf, element_type);
+ for (i = 0; i < ndim; i++)
+ {
+ pq_sendint32(&buf, dim[i]);
+ pq_sendint32(&buf, lb[i]);
+ }
+
+ /* Send the array elements using the element's own sendproc */
+ array_iter_setup(&iter, v);
+
+ for (i = 0; i < nitems; i++)
+ {
+ Datum itemvalue;
+ bool isnull;
+
+ /* Get source element, checking for NULL */
+ itemvalue = array_iter_next(&iter, &isnull, i,
+ typlen, typbyval, typalign);
+
+ if (isnull)
+ {
+ /* -1 length means a NULL */
+ pq_sendint32(&buf, -1);
+ }
+ else
+ {
+ bytea *outputbytes;
+
+ outputbytes = SendFunctionCall(&my_extra->proc, itemvalue);
+ pq_sendint32(&buf, VARSIZE(outputbytes) - VARHDRSZ);
+ pq_sendbytes(&buf, VARDATA(outputbytes),
+ VARSIZE(outputbytes) - VARHDRSZ);
+ pfree(outputbytes);
+ }
+ }
+
+ PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
+}
+
+/*
+ * array_ndims :
+ * returns the number of dimensions of the array pointed to by "v"
+ */
+Datum
+array_ndims(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *v = PG_GETARG_ANY_ARRAY_P(0);
+
+ /* Sanity check: does it look like an array at all? */
+ if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM)
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT32(AARR_NDIM(v));
+}
+
+/*
+ * array_dims :
+ * returns the dimensions of the array pointed to by "v", as a "text"
+ */
+Datum
+array_dims(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *v = PG_GETARG_ANY_ARRAY_P(0);
+ char *p;
+ int i;
+ int *dimv,
+ *lb;
+
+ /*
+ * 33 since we assume 15 digits per number + ':' +'[]'
+ *
+ * +1 for trailing null
+ */
+ char buf[MAXDIM * 33 + 1];
+
+ /* Sanity check: does it look like an array at all? */
+ if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM)
+ PG_RETURN_NULL();
+
+ dimv = AARR_DIMS(v);
+ lb = AARR_LBOUND(v);
+
+ p = buf;
+ for (i = 0; i < AARR_NDIM(v); i++)
+ {
+ sprintf(p, "[%d:%d]", lb[i], dimv[i] + lb[i] - 1);
+ p += strlen(p);
+ }
+
+ PG_RETURN_TEXT_P(cstring_to_text(buf));
+}
+
+/*
+ * array_lower :
+ * returns the lower dimension, of the DIM requested, for
+ * the array pointed to by "v", as an int4
+ */
+Datum
+array_lower(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *v = PG_GETARG_ANY_ARRAY_P(0);
+ int reqdim = PG_GETARG_INT32(1);
+ int *lb;
+ int result;
+
+ /* Sanity check: does it look like an array at all? */
+ if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM)
+ PG_RETURN_NULL();
+
+ /* Sanity check: was the requested dim valid */
+ if (reqdim <= 0 || reqdim > AARR_NDIM(v))
+ PG_RETURN_NULL();
+
+ lb = AARR_LBOUND(v);
+ result = lb[reqdim - 1];
+
+ PG_RETURN_INT32(result);
+}
+
+/*
+ * array_upper :
+ * returns the upper dimension, of the DIM requested, for
+ * the array pointed to by "v", as an int4
+ */
+Datum
+array_upper(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *v = PG_GETARG_ANY_ARRAY_P(0);
+ int reqdim = PG_GETARG_INT32(1);
+ int *dimv,
+ *lb;
+ int result;
+
+ /* Sanity check: does it look like an array at all? */
+ if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM)
+ PG_RETURN_NULL();
+
+ /* Sanity check: was the requested dim valid */
+ if (reqdim <= 0 || reqdim > AARR_NDIM(v))
+ PG_RETURN_NULL();
+
+ lb = AARR_LBOUND(v);
+ dimv = AARR_DIMS(v);
+
+ result = dimv[reqdim - 1] + lb[reqdim - 1] - 1;
+
+ PG_RETURN_INT32(result);
+}
+
+/*
+ * array_length :
+ * returns the length, of the dimension requested, for
+ * the array pointed to by "v", as an int4
+ */
+Datum
+array_length(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *v = PG_GETARG_ANY_ARRAY_P(0);
+ int reqdim = PG_GETARG_INT32(1);
+ int *dimv;
+ int result;
+
+ /* Sanity check: does it look like an array at all? */
+ if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM)
+ PG_RETURN_NULL();
+
+ /* Sanity check: was the requested dim valid */
+ if (reqdim <= 0 || reqdim > AARR_NDIM(v))
+ PG_RETURN_NULL();
+
+ dimv = AARR_DIMS(v);
+
+ result = dimv[reqdim - 1];
+
+ PG_RETURN_INT32(result);
+}
+
+/*
+ * array_cardinality:
+ * returns the total number of elements in an array
+ */
+Datum
+array_cardinality(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *v = PG_GETARG_ANY_ARRAY_P(0);
+
+ PG_RETURN_INT32(ArrayGetNItems(AARR_NDIM(v), AARR_DIMS(v)));
+}
+
+
+/*
+ * array_get_element :
+ * This routine takes an array datum and a subscript array and returns
+ * the referenced item as a Datum. Note that for a pass-by-reference
+ * datatype, the returned Datum is a pointer into the array object.
+ *
+ * This handles both ordinary varlena arrays and fixed-length arrays.
+ *
+ * Inputs:
+ * arraydatum: the array object (mustn't be NULL)
+ * nSubscripts: number of subscripts supplied
+ * indx[]: the subscript values
+ * arraytyplen: pg_type.typlen for the array type
+ * elmlen: pg_type.typlen for the array's element type
+ * elmbyval: pg_type.typbyval for the array's element type
+ * elmalign: pg_type.typalign for the array's element type
+ *
+ * Outputs:
+ * The return value is the element Datum.
+ * *isNull is set to indicate whether the element is NULL.
+ */
+Datum
+array_get_element(Datum arraydatum,
+ int nSubscripts,
+ int *indx,
+ int arraytyplen,
+ int elmlen,
+ bool elmbyval,
+ char elmalign,
+ bool *isNull)
+{
+ int i,
+ ndim,
+ *dim,
+ *lb,
+ offset,
+ fixedDim[1],
+ fixedLb[1];
+ char *arraydataptr,
+ *retptr;
+ bits8 *arraynullsptr;
+
+ if (arraytyplen > 0)
+ {
+ /*
+ * fixed-length arrays -- these are assumed to be 1-d, 0-based
+ */
+ ndim = 1;
+ fixedDim[0] = arraytyplen / elmlen;
+ fixedLb[0] = 0;
+ dim = fixedDim;
+ lb = fixedLb;
+ arraydataptr = (char *) DatumGetPointer(arraydatum);
+ arraynullsptr = NULL;
+ }
+ else if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(arraydatum)))
+ {
+ /* expanded array: let's do this in a separate function */
+ return array_get_element_expanded(arraydatum,
+ nSubscripts,
+ indx,
+ arraytyplen,
+ elmlen,
+ elmbyval,
+ elmalign,
+ isNull);
+ }
+ else
+ {
+ /* detoast array if necessary, producing normal varlena input */
+ ArrayType *array = DatumGetArrayTypeP(arraydatum);
+
+ ndim = ARR_NDIM(array);
+ dim = ARR_DIMS(array);
+ lb = ARR_LBOUND(array);
+ arraydataptr = ARR_DATA_PTR(array);
+ arraynullsptr = ARR_NULLBITMAP(array);
+ }
+
+ /*
+ * Return NULL for invalid subscript
+ */
+ if (ndim != nSubscripts || ndim <= 0 || ndim > MAXDIM)
+ {
+ *isNull = true;
+ return (Datum) 0;
+ }
+ for (i = 0; i < ndim; i++)
+ {
+ if (indx[i] < lb[i] || indx[i] >= (dim[i] + lb[i]))
+ {
+ *isNull = true;
+ return (Datum) 0;
+ }
+ }
+
+ /*
+ * Calculate the element number
+ */
+ offset = ArrayGetOffset(nSubscripts, dim, lb, indx);
+
+ /*
+ * Check for NULL array element
+ */
+ if (array_get_isnull(arraynullsptr, offset))
+ {
+ *isNull = true;
+ return (Datum) 0;
+ }
+
+ /*
+ * OK, get the element
+ */
+ *isNull = false;
+ retptr = array_seek(arraydataptr, 0, arraynullsptr, offset,
+ elmlen, elmbyval, elmalign);
+ return ArrayCast(retptr, elmbyval, elmlen);
+}
+
+/*
+ * Implementation of array_get_element() for an expanded array
+ */
+static Datum
+array_get_element_expanded(Datum arraydatum,
+ int nSubscripts, int *indx,
+ int arraytyplen,
+ int elmlen, bool elmbyval, char elmalign,
+ bool *isNull)
+{
+ ExpandedArrayHeader *eah;
+ int i,
+ ndim,
+ *dim,
+ *lb,
+ offset;
+ Datum *dvalues;
+ bool *dnulls;
+
+ eah = (ExpandedArrayHeader *) DatumGetEOHP(arraydatum);
+ Assert(eah->ea_magic == EA_MAGIC);
+
+ /* sanity-check caller's info against object */
+ Assert(arraytyplen == -1);
+ Assert(elmlen == eah->typlen);
+ Assert(elmbyval == eah->typbyval);
+ Assert(elmalign == eah->typalign);
+
+ ndim = eah->ndims;
+ dim = eah->dims;
+ lb = eah->lbound;
+
+ /*
+ * Return NULL for invalid subscript
+ */
+ if (ndim != nSubscripts || ndim <= 0 || ndim > MAXDIM)
+ {
+ *isNull = true;
+ return (Datum) 0;
+ }
+ for (i = 0; i < ndim; i++)
+ {
+ if (indx[i] < lb[i] || indx[i] >= (dim[i] + lb[i]))
+ {
+ *isNull = true;
+ return (Datum) 0;
+ }
+ }
+
+ /*
+ * Calculate the element number
+ */
+ offset = ArrayGetOffset(nSubscripts, dim, lb, indx);
+
+ /*
+ * Deconstruct array if we didn't already. Note that we apply this even
+ * if the input is nominally read-only: it should be safe enough.
+ */
+ deconstruct_expanded_array(eah);
+
+ dvalues = eah->dvalues;
+ dnulls = eah->dnulls;
+
+ /*
+ * Check for NULL array element
+ */
+ if (dnulls && dnulls[offset])
+ {
+ *isNull = true;
+ return (Datum) 0;
+ }
+
+ /*
+ * OK, get the element. It's OK to return a pass-by-ref value as a
+ * pointer into the expanded array, for the same reason that regular
+ * array_get_element can return a pointer into flat arrays: the value is
+ * assumed not to change for as long as the Datum reference can exist.
+ */
+ *isNull = false;
+ return dvalues[offset];
+}
+
+/*
+ * array_get_slice :
+ * This routine takes an array and a range of indices (upperIndx and
+ * lowerIndx), creates a new array structure for the referred elements
+ * and returns a pointer to it.
+ *
+ * This handles both ordinary varlena arrays and fixed-length arrays.
+ *
+ * Inputs:
+ * arraydatum: the array object (mustn't be NULL)
+ * nSubscripts: number of subscripts supplied (must be same for upper/lower)
+ * upperIndx[]: the upper subscript values
+ * lowerIndx[]: the lower subscript values
+ * upperProvided[]: true for provided upper subscript values
+ * lowerProvided[]: true for provided lower subscript values
+ * arraytyplen: pg_type.typlen for the array type
+ * elmlen: pg_type.typlen for the array's element type
+ * elmbyval: pg_type.typbyval for the array's element type
+ * elmalign: pg_type.typalign for the array's element type
+ *
+ * Outputs:
+ * The return value is the new array Datum (it's never NULL)
+ *
+ * Omitted upper and lower subscript values are replaced by the corresponding
+ * array bound.
+ *
+ * NOTE: we assume it is OK to scribble on the provided subscript arrays
+ * lowerIndx[] and upperIndx[]; also, these arrays must be of size MAXDIM
+ * even when nSubscripts is less. These are generally just temporaries.
+ */
+Datum
+array_get_slice(Datum arraydatum,
+ int nSubscripts,
+ int *upperIndx,
+ int *lowerIndx,
+ bool *upperProvided,
+ bool *lowerProvided,
+ int arraytyplen,
+ int elmlen,
+ bool elmbyval,
+ char elmalign)
+{
+ ArrayType *array;
+ ArrayType *newarray;
+ int i,
+ ndim,
+ *dim,
+ *lb,
+ *newlb;
+ int fixedDim[1],
+ fixedLb[1];
+ Oid elemtype;
+ char *arraydataptr;
+ bits8 *arraynullsptr;
+ int32 dataoffset;
+ int bytes,
+ span[MAXDIM];
+
+ if (arraytyplen > 0)
+ {
+ /*
+ * fixed-length arrays -- currently, cannot slice these because parser
+ * labels output as being of the fixed-length array type! Code below
+ * shows how we could support it if the parser were changed to label
+ * output as a suitable varlena array type.
+ */
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("slices of fixed-length arrays not implemented")));
+
+ /*
+ * fixed-length arrays -- these are assumed to be 1-d, 0-based
+ *
+ * XXX where would we get the correct ELEMTYPE from?
+ */
+ ndim = 1;
+ fixedDim[0] = arraytyplen / elmlen;
+ fixedLb[0] = 0;
+ dim = fixedDim;
+ lb = fixedLb;
+ elemtype = InvalidOid; /* XXX */
+ arraydataptr = (char *) DatumGetPointer(arraydatum);
+ arraynullsptr = NULL;
+ }
+ else
+ {
+ /* detoast input array if necessary */
+ array = DatumGetArrayTypeP(arraydatum);
+
+ ndim = ARR_NDIM(array);
+ dim = ARR_DIMS(array);
+ lb = ARR_LBOUND(array);
+ elemtype = ARR_ELEMTYPE(array);
+ arraydataptr = ARR_DATA_PTR(array);
+ arraynullsptr = ARR_NULLBITMAP(array);
+ }
+
+ /*
+ * Check provided subscripts. A slice exceeding the current array limits
+ * is silently truncated to the array limits. If we end up with an empty
+ * slice, return an empty array.
+ */
+ if (ndim < nSubscripts || ndim <= 0 || ndim > MAXDIM)
+ return PointerGetDatum(construct_empty_array(elemtype));
+
+ for (i = 0; i < nSubscripts; i++)
+ {
+ if (!lowerProvided[i] || lowerIndx[i] < lb[i])
+ lowerIndx[i] = lb[i];
+ if (!upperProvided[i] || upperIndx[i] >= (dim[i] + lb[i]))
+ upperIndx[i] = dim[i] + lb[i] - 1;
+ if (lowerIndx[i] > upperIndx[i])
+ return PointerGetDatum(construct_empty_array(elemtype));
+ }
+ /* fill any missing subscript positions with full array range */
+ for (; i < ndim; i++)
+ {
+ lowerIndx[i] = lb[i];
+ upperIndx[i] = dim[i] + lb[i] - 1;
+ if (lowerIndx[i] > upperIndx[i])
+ return PointerGetDatum(construct_empty_array(elemtype));
+ }
+
+ mda_get_range(ndim, span, lowerIndx, upperIndx);
+
+ bytes = array_slice_size(arraydataptr, arraynullsptr,
+ ndim, dim, lb,
+ lowerIndx, upperIndx,
+ elmlen, elmbyval, elmalign);
+
+ /*
+ * Currently, we put a null bitmap in the result if the source has one;
+ * could be smarter ...
+ */
+ if (arraynullsptr)
+ {
+ dataoffset = ARR_OVERHEAD_WITHNULLS(ndim, ArrayGetNItems(ndim, span));
+ bytes += dataoffset;
+ }
+ else
+ {
+ dataoffset = 0; /* marker for no null bitmap */
+ bytes += ARR_OVERHEAD_NONULLS(ndim);
+ }
+
+ newarray = (ArrayType *) palloc0(bytes);
+ SET_VARSIZE(newarray, bytes);
+ newarray->ndim = ndim;
+ newarray->dataoffset = dataoffset;
+ newarray->elemtype = elemtype;
+ memcpy(ARR_DIMS(newarray), span, ndim * sizeof(int));
+
+ /*
+ * Lower bounds of the new array are set to 1. Formerly (before 7.3) we
+ * copied the given lowerIndx values ... but that seems confusing.
+ */
+ newlb = ARR_LBOUND(newarray);
+ for (i = 0; i < ndim; i++)
+ newlb[i] = 1;
+
+ array_extract_slice(newarray,
+ ndim, dim, lb,
+ arraydataptr, arraynullsptr,
+ lowerIndx, upperIndx,
+ elmlen, elmbyval, elmalign);
+
+ return PointerGetDatum(newarray);
+}
+
+/*
+ * array_set_element :
+ * This routine sets the value of one array element (specified by
+ * a subscript array) to a new value specified by "dataValue".
+ *
+ * This handles both ordinary varlena arrays and fixed-length arrays.
+ *
+ * Inputs:
+ * arraydatum: the initial array object (mustn't be NULL)
+ * nSubscripts: number of subscripts supplied
+ * indx[]: the subscript values
+ * dataValue: the datum to be inserted at the given position
+ * isNull: whether dataValue is NULL
+ * arraytyplen: pg_type.typlen for the array type
+ * elmlen: pg_type.typlen for the array's element type
+ * elmbyval: pg_type.typbyval for the array's element type
+ * elmalign: pg_type.typalign for the array's element type
+ *
+ * Result:
+ * A new array is returned, just like the old except for the one
+ * modified entry. The original array object is not changed,
+ * unless what is passed is a read-write reference to an expanded
+ * array object; in that case the expanded array is updated in-place.
+ *
+ * For one-dimensional arrays only, we allow the array to be extended
+ * by assigning to a position outside the existing subscript range; any
+ * positions between the existing elements and the new one are set to NULLs.
+ * (XXX TODO: allow a corresponding behavior for multidimensional arrays)
+ *
+ * NOTE: For assignments, we throw an error for invalid subscripts etc,
+ * rather than returning a NULL as the fetch operations do.
+ */
+Datum
+array_set_element(Datum arraydatum,
+ int nSubscripts,
+ int *indx,
+ Datum dataValue,
+ bool isNull,
+ int arraytyplen,
+ int elmlen,
+ bool elmbyval,
+ char elmalign)
+{
+ ArrayType *array;
+ ArrayType *newarray;
+ int i,
+ ndim,
+ dim[MAXDIM],
+ lb[MAXDIM],
+ offset;
+ char *elt_ptr;
+ bool newhasnulls;
+ bits8 *oldnullbitmap;
+ int oldnitems,
+ newnitems,
+ olddatasize,
+ newsize,
+ olditemlen,
+ newitemlen,
+ overheadlen,
+ oldoverheadlen,
+ addedbefore,
+ addedafter,
+ lenbefore,
+ lenafter;
+
+ if (arraytyplen > 0)
+ {
+ /*
+ * fixed-length arrays -- these are assumed to be 1-d, 0-based. We
+ * cannot extend them, either.
+ */
+ char *resultarray;
+
+ if (nSubscripts != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("wrong number of array subscripts")));
+
+ if (indx[0] < 0 || indx[0] >= arraytyplen / elmlen)
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("array subscript out of range")));
+
+ if (isNull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("cannot assign null value to an element of a fixed-length array")));
+
+ resultarray = (char *) palloc(arraytyplen);
+ memcpy(resultarray, DatumGetPointer(arraydatum), arraytyplen);
+ elt_ptr = (char *) resultarray + indx[0] * elmlen;
+ ArrayCastAndSet(dataValue, elmlen, elmbyval, elmalign, elt_ptr);
+ return PointerGetDatum(resultarray);
+ }
+
+ if (nSubscripts <= 0 || nSubscripts > MAXDIM)
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("wrong number of array subscripts")));
+
+ /* make sure item to be inserted is not toasted */
+ if (elmlen == -1 && !isNull)
+ dataValue = PointerGetDatum(PG_DETOAST_DATUM(dataValue));
+
+ if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(arraydatum)))
+ {
+ /* expanded array: let's do this in a separate function */
+ return array_set_element_expanded(arraydatum,
+ nSubscripts,
+ indx,
+ dataValue,
+ isNull,
+ arraytyplen,
+ elmlen,
+ elmbyval,
+ elmalign);
+ }
+
+ /* detoast input array if necessary */
+ array = DatumGetArrayTypeP(arraydatum);
+
+ ndim = ARR_NDIM(array);
+
+ /*
+ * if number of dims is zero, i.e. an empty array, create an array with
+ * nSubscripts dimensions, and set the lower bounds to the supplied
+ * subscripts
+ */
+ if (ndim == 0)
+ {
+ Oid elmtype = ARR_ELEMTYPE(array);
+
+ for (i = 0; i < nSubscripts; i++)
+ {
+ dim[i] = 1;
+ lb[i] = indx[i];
+ }
+
+ return PointerGetDatum(construct_md_array(&dataValue, &isNull,
+ nSubscripts, dim, lb,
+ elmtype,
+ elmlen, elmbyval, elmalign));
+ }
+
+ if (ndim != nSubscripts)
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("wrong number of array subscripts")));
+
+ /* copy dim/lb since we may modify them */
+ memcpy(dim, ARR_DIMS(array), ndim * sizeof(int));
+ memcpy(lb, ARR_LBOUND(array), ndim * sizeof(int));
+
+ newhasnulls = (ARR_HASNULL(array) || isNull);
+ addedbefore = addedafter = 0;
+
+ /*
+ * Check subscripts
+ */
+ if (ndim == 1)
+ {
+ if (indx[0] < lb[0])
+ {
+ addedbefore = lb[0] - indx[0];
+ dim[0] += addedbefore;
+ lb[0] = indx[0];
+ if (addedbefore > 1)
+ newhasnulls = true; /* will insert nulls */
+ }
+ if (indx[0] >= (dim[0] + lb[0]))
+ {
+ addedafter = indx[0] - (dim[0] + lb[0]) + 1;
+ dim[0] += addedafter;
+ if (addedafter > 1)
+ newhasnulls = true; /* will insert nulls */
+ }
+ }
+ else
+ {
+ /*
+ * XXX currently we do not support extending multi-dimensional arrays
+ * during assignment
+ */
+ for (i = 0; i < ndim; i++)
+ {
+ if (indx[i] < lb[i] ||
+ indx[i] >= (dim[i] + lb[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("array subscript out of range")));
+ }
+ }
+
+ /* This checks for overflow of the array dimensions */
+ newnitems = ArrayGetNItems(ndim, dim);
+ ArrayCheckBounds(ndim, dim, lb);
+
+ /*
+ * Compute sizes of items and areas to copy
+ */
+ if (newhasnulls)
+ overheadlen = ARR_OVERHEAD_WITHNULLS(ndim, newnitems);
+ else
+ overheadlen = ARR_OVERHEAD_NONULLS(ndim);
+ oldnitems = ArrayGetNItems(ndim, ARR_DIMS(array));
+ oldnullbitmap = ARR_NULLBITMAP(array);
+ oldoverheadlen = ARR_DATA_OFFSET(array);
+ olddatasize = ARR_SIZE(array) - oldoverheadlen;
+ if (addedbefore)
+ {
+ offset = 0;
+ lenbefore = 0;
+ olditemlen = 0;
+ lenafter = olddatasize;
+ }
+ else if (addedafter)
+ {
+ offset = oldnitems;
+ lenbefore = olddatasize;
+ olditemlen = 0;
+ lenafter = 0;
+ }
+ else
+ {
+ offset = ArrayGetOffset(nSubscripts, dim, lb, indx);
+ elt_ptr = array_seek(ARR_DATA_PTR(array), 0, oldnullbitmap, offset,
+ elmlen, elmbyval, elmalign);
+ lenbefore = (int) (elt_ptr - ARR_DATA_PTR(array));
+ if (array_get_isnull(oldnullbitmap, offset))
+ olditemlen = 0;
+ else
+ {
+ olditemlen = att_addlength_pointer(0, elmlen, elt_ptr);
+ olditemlen = att_align_nominal(olditemlen, elmalign);
+ }
+ lenafter = (int) (olddatasize - lenbefore - olditemlen);
+ }
+
+ if (isNull)
+ newitemlen = 0;
+ else
+ {
+ newitemlen = att_addlength_datum(0, elmlen, dataValue);
+ newitemlen = att_align_nominal(newitemlen, elmalign);
+ }
+
+ newsize = overheadlen + lenbefore + newitemlen + lenafter;
+
+ /*
+ * OK, create the new array and fill in header/dimensions
+ */
+ newarray = (ArrayType *) palloc0(newsize);
+ SET_VARSIZE(newarray, newsize);
+ newarray->ndim = ndim;
+ newarray->dataoffset = newhasnulls ? overheadlen : 0;
+ newarray->elemtype = ARR_ELEMTYPE(array);
+ memcpy(ARR_DIMS(newarray), dim, ndim * sizeof(int));
+ memcpy(ARR_LBOUND(newarray), lb, ndim * sizeof(int));
+
+ /*
+ * Fill in data
+ */
+ memcpy((char *) newarray + overheadlen,
+ (char *) array + oldoverheadlen,
+ lenbefore);
+ if (!isNull)
+ ArrayCastAndSet(dataValue, elmlen, elmbyval, elmalign,
+ (char *) newarray + overheadlen + lenbefore);
+ memcpy((char *) newarray + overheadlen + lenbefore + newitemlen,
+ (char *) array + oldoverheadlen + lenbefore + olditemlen,
+ lenafter);
+
+ /*
+ * Fill in nulls bitmap if needed
+ *
+ * Note: it's possible we just replaced the last NULL with a non-NULL, and
+ * could get rid of the bitmap. Seems not worth testing for though.
+ */
+ if (newhasnulls)
+ {
+ bits8 *newnullbitmap = ARR_NULLBITMAP(newarray);
+
+ /* Zero the bitmap to take care of marking inserted positions null */
+ MemSet(newnullbitmap, 0, (newnitems + 7) / 8);
+ /* Fix the inserted value */
+ if (addedafter)
+ array_set_isnull(newnullbitmap, newnitems - 1, isNull);
+ else
+ array_set_isnull(newnullbitmap, offset, isNull);
+ /* Fix the copied range(s) */
+ if (addedbefore)
+ array_bitmap_copy(newnullbitmap, addedbefore,
+ oldnullbitmap, 0,
+ oldnitems);
+ else
+ {
+ array_bitmap_copy(newnullbitmap, 0,
+ oldnullbitmap, 0,
+ offset);
+ if (addedafter == 0)
+ array_bitmap_copy(newnullbitmap, offset + 1,
+ oldnullbitmap, offset + 1,
+ oldnitems - offset - 1);
+ }
+ }
+
+ return PointerGetDatum(newarray);
+}
+
+/*
+ * Implementation of array_set_element() for an expanded array
+ *
+ * Note: as with any operation on a read/write expanded object, we must
+ * take pains not to leave the object in a corrupt state if we fail partway
+ * through.
+ */
+static Datum
+array_set_element_expanded(Datum arraydatum,
+ int nSubscripts, int *indx,
+ Datum dataValue, bool isNull,
+ int arraytyplen,
+ int elmlen, bool elmbyval, char elmalign)
+{
+ ExpandedArrayHeader *eah;
+ Datum *dvalues;
+ bool *dnulls;
+ int i,
+ ndim,
+ dim[MAXDIM],
+ lb[MAXDIM],
+ offset;
+ bool dimschanged,
+ newhasnulls;
+ int addedbefore,
+ addedafter;
+ char *oldValue;
+
+ /* Convert to R/W object if not so already */
+ eah = DatumGetExpandedArray(arraydatum);
+
+ /* Sanity-check caller's info against object; we don't use it otherwise */
+ Assert(arraytyplen == -1);
+ Assert(elmlen == eah->typlen);
+ Assert(elmbyval == eah->typbyval);
+ Assert(elmalign == eah->typalign);
+
+ /*
+ * Copy dimension info into local storage. This allows us to modify the
+ * dimensions if needed, while not messing up the expanded value if we
+ * fail partway through.
+ */
+ ndim = eah->ndims;
+ Assert(ndim >= 0 && ndim <= MAXDIM);
+ memcpy(dim, eah->dims, ndim * sizeof(int));
+ memcpy(lb, eah->lbound, ndim * sizeof(int));
+ dimschanged = false;
+
+ /*
+ * if number of dims is zero, i.e. an empty array, create an array with
+ * nSubscripts dimensions, and set the lower bounds to the supplied
+ * subscripts.
+ */
+ if (ndim == 0)
+ {
+ /*
+ * Allocate adequate space for new dimension info. This is harmless
+ * if we fail later.
+ */
+ Assert(nSubscripts > 0 && nSubscripts <= MAXDIM);
+ eah->dims = (int *) MemoryContextAllocZero(eah->hdr.eoh_context,
+ nSubscripts * sizeof(int));
+ eah->lbound = (int *) MemoryContextAllocZero(eah->hdr.eoh_context,
+ nSubscripts * sizeof(int));
+
+ /* Update local copies of dimension info */
+ ndim = nSubscripts;
+ for (i = 0; i < nSubscripts; i++)
+ {
+ dim[i] = 0;
+ lb[i] = indx[i];
+ }
+ dimschanged = true;
+ }
+ else if (ndim != nSubscripts)
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("wrong number of array subscripts")));
+
+ /*
+ * Deconstruct array if we didn't already. (Someday maybe add a special
+ * case path for fixed-length, no-nulls cases, where we can overwrite an
+ * element in place without ever deconstructing. But today is not that
+ * day.)
+ */
+ deconstruct_expanded_array(eah);
+
+ /*
+ * Copy new element into array's context, if needed (we assume it's
+ * already detoasted, so no junk should be created). Doing this before
+ * we've made any significant changes ensures that our behavior is sane
+ * even when the source is a reference to some element of this same array.
+ * If we fail further down, this memory is leaked, but that's reasonably
+ * harmless.
+ */
+ if (!eah->typbyval && !isNull)
+ {
+ MemoryContext oldcxt = MemoryContextSwitchTo(eah->hdr.eoh_context);
+
+ dataValue = datumCopy(dataValue, false, eah->typlen);
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ dvalues = eah->dvalues;
+ dnulls = eah->dnulls;
+
+ newhasnulls = ((dnulls != NULL) || isNull);
+ addedbefore = addedafter = 0;
+
+ /*
+ * Check subscripts (this logic matches original array_set_element)
+ */
+ if (ndim == 1)
+ {
+ if (indx[0] < lb[0])
+ {
+ addedbefore = lb[0] - indx[0];
+ dim[0] += addedbefore;
+ lb[0] = indx[0];
+ dimschanged = true;
+ if (addedbefore > 1)
+ newhasnulls = true; /* will insert nulls */
+ }
+ if (indx[0] >= (dim[0] + lb[0]))
+ {
+ addedafter = indx[0] - (dim[0] + lb[0]) + 1;
+ dim[0] += addedafter;
+ dimschanged = true;
+ if (addedafter > 1)
+ newhasnulls = true; /* will insert nulls */
+ }
+ }
+ else
+ {
+ /*
+ * XXX currently we do not support extending multi-dimensional arrays
+ * during assignment
+ */
+ for (i = 0; i < ndim; i++)
+ {
+ if (indx[i] < lb[i] ||
+ indx[i] >= (dim[i] + lb[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("array subscript out of range")));
+ }
+ }
+
+ /* Check for overflow of the array dimensions */
+ if (dimschanged)
+ {
+ (void) ArrayGetNItems(ndim, dim);
+ ArrayCheckBounds(ndim, dim, lb);
+ }
+
+ /* Now we can calculate linear offset of target item in array */
+ offset = ArrayGetOffset(nSubscripts, dim, lb, indx);
+
+ /* Physically enlarge existing dvalues/dnulls arrays if needed */
+ if (dim[0] > eah->dvalueslen)
+ {
+ /* We want some extra space if we're enlarging */
+ int newlen = dim[0] + dim[0] / 8;
+
+ newlen = Max(newlen, dim[0]); /* integer overflow guard */
+ eah->dvalues = dvalues = (Datum *)
+ repalloc(dvalues, newlen * sizeof(Datum));
+ if (dnulls)
+ eah->dnulls = dnulls = (bool *)
+ repalloc(dnulls, newlen * sizeof(bool));
+ eah->dvalueslen = newlen;
+ }
+
+ /*
+ * If we need a nulls bitmap and don't already have one, create it, being
+ * sure to mark all existing entries as not null.
+ */
+ if (newhasnulls && dnulls == NULL)
+ eah->dnulls = dnulls = (bool *)
+ MemoryContextAllocZero(eah->hdr.eoh_context,
+ eah->dvalueslen * sizeof(bool));
+
+ /*
+ * We now have all the needed space allocated, so we're ready to make
+ * irreversible changes. Be very wary of allowing failure below here.
+ */
+
+ /* Flattened value will no longer represent array accurately */
+ eah->fvalue = NULL;
+ /* And we don't know the flattened size either */
+ eah->flat_size = 0;
+
+ /* Update dimensionality info if needed */
+ if (dimschanged)
+ {
+ eah->ndims = ndim;
+ memcpy(eah->dims, dim, ndim * sizeof(int));
+ memcpy(eah->lbound, lb, ndim * sizeof(int));
+ }
+
+ /* Reposition items if needed, and fill addedbefore items with nulls */
+ if (addedbefore > 0)
+ {
+ memmove(dvalues + addedbefore, dvalues, eah->nelems * sizeof(Datum));
+ for (i = 0; i < addedbefore; i++)
+ dvalues[i] = (Datum) 0;
+ if (dnulls)
+ {
+ memmove(dnulls + addedbefore, dnulls, eah->nelems * sizeof(bool));
+ for (i = 0; i < addedbefore; i++)
+ dnulls[i] = true;
+ }
+ eah->nelems += addedbefore;
+ }
+
+ /* fill addedafter items with nulls */
+ if (addedafter > 0)
+ {
+ for (i = 0; i < addedafter; i++)
+ dvalues[eah->nelems + i] = (Datum) 0;
+ if (dnulls)
+ {
+ for (i = 0; i < addedafter; i++)
+ dnulls[eah->nelems + i] = true;
+ }
+ eah->nelems += addedafter;
+ }
+
+ /* Grab old element value for pfree'ing, if needed. */
+ if (!eah->typbyval && (dnulls == NULL || !dnulls[offset]))
+ oldValue = (char *) DatumGetPointer(dvalues[offset]);
+ else
+ oldValue = NULL;
+
+ /* And finally we can insert the new element. */
+ dvalues[offset] = dataValue;
+ if (dnulls)
+ dnulls[offset] = isNull;
+
+ /*
+ * Free old element if needed; this keeps repeated element replacements
+ * from bloating the array's storage. If the pfree somehow fails, it
+ * won't corrupt the array.
+ */
+ if (oldValue)
+ {
+ /* Don't try to pfree a part of the original flat array */
+ if (oldValue < eah->fstartptr || oldValue >= eah->fendptr)
+ pfree(oldValue);
+ }
+
+ /* Done, return standard TOAST pointer for object */
+ return EOHPGetRWDatum(&eah->hdr);
+}
+
+/*
+ * array_set_slice :
+ * This routine sets the value of a range of array locations (specified
+ * by upper and lower subscript values) to new values passed as
+ * another array.
+ *
+ * This handles both ordinary varlena arrays and fixed-length arrays.
+ *
+ * Inputs:
+ * arraydatum: the initial array object (mustn't be NULL)
+ * nSubscripts: number of subscripts supplied (must be same for upper/lower)
+ * upperIndx[]: the upper subscript values
+ * lowerIndx[]: the lower subscript values
+ * upperProvided[]: true for provided upper subscript values
+ * lowerProvided[]: true for provided lower subscript values
+ * srcArrayDatum: the source for the inserted values
+ * isNull: indicates whether srcArrayDatum is NULL
+ * arraytyplen: pg_type.typlen for the array type
+ * elmlen: pg_type.typlen for the array's element type
+ * elmbyval: pg_type.typbyval for the array's element type
+ * elmalign: pg_type.typalign for the array's element type
+ *
+ * Result:
+ * A new array is returned, just like the old except for the
+ * modified range. The original array object is not changed.
+ *
+ * Omitted upper and lower subscript values are replaced by the corresponding
+ * array bound.
+ *
+ * For one-dimensional arrays only, we allow the array to be extended
+ * by assigning to positions outside the existing subscript range; any
+ * positions between the existing elements and the new ones are set to NULLs.
+ * (XXX TODO: allow a corresponding behavior for multidimensional arrays)
+ *
+ * NOTE: we assume it is OK to scribble on the provided index arrays
+ * lowerIndx[] and upperIndx[]; also, these arrays must be of size MAXDIM
+ * even when nSubscripts is less. These are generally just temporaries.
+ *
+ * NOTE: For assignments, we throw an error for silly subscripts etc,
+ * rather than returning a NULL or empty array as the fetch operations do.
+ */
+Datum
+array_set_slice(Datum arraydatum,
+ int nSubscripts,
+ int *upperIndx,
+ int *lowerIndx,
+ bool *upperProvided,
+ bool *lowerProvided,
+ Datum srcArrayDatum,
+ bool isNull,
+ int arraytyplen,
+ int elmlen,
+ bool elmbyval,
+ char elmalign)
+{
+ ArrayType *array;
+ ArrayType *srcArray;
+ ArrayType *newarray;
+ int i,
+ ndim,
+ dim[MAXDIM],
+ lb[MAXDIM],
+ span[MAXDIM];
+ bool newhasnulls;
+ int nitems,
+ nsrcitems,
+ olddatasize,
+ newsize,
+ olditemsize,
+ newitemsize,
+ overheadlen,
+ oldoverheadlen,
+ addedbefore,
+ addedafter,
+ lenbefore,
+ lenafter,
+ itemsbefore,
+ itemsafter,
+ nolditems;
+
+ /* Currently, assignment from a NULL source array is a no-op */
+ if (isNull)
+ return arraydatum;
+
+ if (arraytyplen > 0)
+ {
+ /*
+ * fixed-length arrays -- not got round to doing this...
+ */
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("updates on slices of fixed-length arrays not implemented")));
+ }
+
+ /* detoast arrays if necessary */
+ array = DatumGetArrayTypeP(arraydatum);
+ srcArray = DatumGetArrayTypeP(srcArrayDatum);
+
+ /* note: we assume srcArray contains no toasted elements */
+
+ ndim = ARR_NDIM(array);
+
+ /*
+ * if number of dims is zero, i.e. an empty array, create an array with
+ * nSubscripts dimensions, and set the upper and lower bounds to the
+ * supplied subscripts
+ */
+ if (ndim == 0)
+ {
+ Datum *dvalues;
+ bool *dnulls;
+ int nelems;
+ Oid elmtype = ARR_ELEMTYPE(array);
+
+ deconstruct_array(srcArray, elmtype, elmlen, elmbyval, elmalign,
+ &dvalues, &dnulls, &nelems);
+
+ for (i = 0; i < nSubscripts; i++)
+ {
+ if (!upperProvided[i] || !lowerProvided[i])
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("array slice subscript must provide both boundaries"),
+ errdetail("When assigning to a slice of an empty array value,"
+ " slice boundaries must be fully specified.")));
+
+ dim[i] = 1 + upperIndx[i] - lowerIndx[i];
+ lb[i] = lowerIndx[i];
+ }
+
+ /* complain if too few source items; we ignore extras, however */
+ if (nelems < ArrayGetNItems(nSubscripts, dim))
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("source array too small")));
+
+ return PointerGetDatum(construct_md_array(dvalues, dnulls, nSubscripts,
+ dim, lb, elmtype,
+ elmlen, elmbyval, elmalign));
+ }
+
+ if (ndim < nSubscripts || ndim <= 0 || ndim > MAXDIM)
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("wrong number of array subscripts")));
+
+ /* copy dim/lb since we may modify them */
+ memcpy(dim, ARR_DIMS(array), ndim * sizeof(int));
+ memcpy(lb, ARR_LBOUND(array), ndim * sizeof(int));
+
+ newhasnulls = (ARR_HASNULL(array) || ARR_HASNULL(srcArray));
+ addedbefore = addedafter = 0;
+
+ /*
+ * Check subscripts
+ */
+ if (ndim == 1)
+ {
+ Assert(nSubscripts == 1);
+ if (!lowerProvided[0])
+ lowerIndx[0] = lb[0];
+ if (!upperProvided[0])
+ upperIndx[0] = dim[0] + lb[0] - 1;
+ if (lowerIndx[0] > upperIndx[0])
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("upper bound cannot be less than lower bound")));
+ if (lowerIndx[0] < lb[0])
+ {
+ if (upperIndx[0] < lb[0] - 1)
+ newhasnulls = true; /* will insert nulls */
+ addedbefore = lb[0] - lowerIndx[0];
+ dim[0] += addedbefore;
+ lb[0] = lowerIndx[0];
+ }
+ if (upperIndx[0] >= (dim[0] + lb[0]))
+ {
+ if (lowerIndx[0] > (dim[0] + lb[0]))
+ newhasnulls = true; /* will insert nulls */
+ addedafter = upperIndx[0] - (dim[0] + lb[0]) + 1;
+ dim[0] += addedafter;
+ }
+ }
+ else
+ {
+ /*
+ * XXX currently we do not support extending multi-dimensional arrays
+ * during assignment
+ */
+ for (i = 0; i < nSubscripts; i++)
+ {
+ if (!lowerProvided[i])
+ lowerIndx[i] = lb[i];
+ if (!upperProvided[i])
+ upperIndx[i] = dim[i] + lb[i] - 1;
+ if (lowerIndx[i] > upperIndx[i])
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("upper bound cannot be less than lower bound")));
+ if (lowerIndx[i] < lb[i] ||
+ upperIndx[i] >= (dim[i] + lb[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("array subscript out of range")));
+ }
+ /* fill any missing subscript positions with full array range */
+ for (; i < ndim; i++)
+ {
+ lowerIndx[i] = lb[i];
+ upperIndx[i] = dim[i] + lb[i] - 1;
+ if (lowerIndx[i] > upperIndx[i])
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("upper bound cannot be less than lower bound")));
+ }
+ }
+
+ /* Do this mainly to check for overflow */
+ nitems = ArrayGetNItems(ndim, dim);
+ ArrayCheckBounds(ndim, dim, lb);
+
+ /*
+ * Make sure source array has enough entries. Note we ignore the shape of
+ * the source array and just read entries serially.
+ */
+ mda_get_range(ndim, span, lowerIndx, upperIndx);
+ nsrcitems = ArrayGetNItems(ndim, span);
+ if (nsrcitems > ArrayGetNItems(ARR_NDIM(srcArray), ARR_DIMS(srcArray)))
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("source array too small")));
+
+ /*
+ * Compute space occupied by new entries, space occupied by replaced
+ * entries, and required space for new array.
+ */
+ if (newhasnulls)
+ overheadlen = ARR_OVERHEAD_WITHNULLS(ndim, nitems);
+ else
+ overheadlen = ARR_OVERHEAD_NONULLS(ndim);
+ newitemsize = array_nelems_size(ARR_DATA_PTR(srcArray), 0,
+ ARR_NULLBITMAP(srcArray), nsrcitems,
+ elmlen, elmbyval, elmalign);
+ oldoverheadlen = ARR_DATA_OFFSET(array);
+ olddatasize = ARR_SIZE(array) - oldoverheadlen;
+ if (ndim > 1)
+ {
+ /*
+ * here we do not need to cope with extension of the array; it would
+ * be a lot more complicated if we had to do so...
+ */
+ olditemsize = array_slice_size(ARR_DATA_PTR(array),
+ ARR_NULLBITMAP(array),
+ ndim, dim, lb,
+ lowerIndx, upperIndx,
+ elmlen, elmbyval, elmalign);
+ lenbefore = lenafter = 0; /* keep compiler quiet */
+ itemsbefore = itemsafter = nolditems = 0;
+ }
+ else
+ {
+ /*
+ * here we must allow for possibility of slice larger than orig array
+ * and/or not adjacent to orig array subscripts
+ */
+ int oldlb = ARR_LBOUND(array)[0];
+ int oldub = oldlb + ARR_DIMS(array)[0] - 1;
+ int slicelb = Max(oldlb, lowerIndx[0]);
+ int sliceub = Min(oldub, upperIndx[0]);
+ char *oldarraydata = ARR_DATA_PTR(array);
+ bits8 *oldarraybitmap = ARR_NULLBITMAP(array);
+
+ /* count/size of old array entries that will go before the slice */
+ itemsbefore = Min(slicelb, oldub + 1) - oldlb;
+ lenbefore = array_nelems_size(oldarraydata, 0, oldarraybitmap,
+ itemsbefore,
+ elmlen, elmbyval, elmalign);
+ /* count/size of old array entries that will be replaced by slice */
+ if (slicelb > sliceub)
+ {
+ nolditems = 0;
+ olditemsize = 0;
+ }
+ else
+ {
+ nolditems = sliceub - slicelb + 1;
+ olditemsize = array_nelems_size(oldarraydata + lenbefore,
+ itemsbefore, oldarraybitmap,
+ nolditems,
+ elmlen, elmbyval, elmalign);
+ }
+ /* count/size of old array entries that will go after the slice */
+ itemsafter = oldub + 1 - Max(sliceub + 1, oldlb);
+ lenafter = olddatasize - lenbefore - olditemsize;
+ }
+
+ newsize = overheadlen + olddatasize - olditemsize + newitemsize;
+
+ newarray = (ArrayType *) palloc0(newsize);
+ SET_VARSIZE(newarray, newsize);
+ newarray->ndim = ndim;
+ newarray->dataoffset = newhasnulls ? overheadlen : 0;
+ newarray->elemtype = ARR_ELEMTYPE(array);
+ memcpy(ARR_DIMS(newarray), dim, ndim * sizeof(int));
+ memcpy(ARR_LBOUND(newarray), lb, ndim * sizeof(int));
+
+ if (ndim > 1)
+ {
+ /*
+ * here we do not need to cope with extension of the array; it would
+ * be a lot more complicated if we had to do so...
+ */
+ array_insert_slice(newarray, array, srcArray,
+ ndim, dim, lb,
+ lowerIndx, upperIndx,
+ elmlen, elmbyval, elmalign);
+ }
+ else
+ {
+ /* fill in data */
+ memcpy((char *) newarray + overheadlen,
+ (char *) array + oldoverheadlen,
+ lenbefore);
+ memcpy((char *) newarray + overheadlen + lenbefore,
+ ARR_DATA_PTR(srcArray),
+ newitemsize);
+ memcpy((char *) newarray + overheadlen + lenbefore + newitemsize,
+ (char *) array + oldoverheadlen + lenbefore + olditemsize,
+ lenafter);
+ /* fill in nulls bitmap if needed */
+ if (newhasnulls)
+ {
+ bits8 *newnullbitmap = ARR_NULLBITMAP(newarray);
+ bits8 *oldnullbitmap = ARR_NULLBITMAP(array);
+
+ /* Zero the bitmap to handle marking inserted positions null */
+ MemSet(newnullbitmap, 0, (nitems + 7) / 8);
+ array_bitmap_copy(newnullbitmap, addedbefore,
+ oldnullbitmap, 0,
+ itemsbefore);
+ array_bitmap_copy(newnullbitmap, lowerIndx[0] - lb[0],
+ ARR_NULLBITMAP(srcArray), 0,
+ nsrcitems);
+ array_bitmap_copy(newnullbitmap, addedbefore + itemsbefore + nolditems,
+ oldnullbitmap, itemsbefore + nolditems,
+ itemsafter);
+ }
+ }
+
+ return PointerGetDatum(newarray);
+}
+
+/*
+ * array_ref : backwards compatibility wrapper for array_get_element
+ *
+ * This only works for detoasted/flattened varlena arrays, since the array
+ * argument is declared as "ArrayType *". However there's enough code like
+ * that to justify preserving this API.
+ */
+Datum
+array_ref(ArrayType *array, int nSubscripts, int *indx,
+ int arraytyplen, int elmlen, bool elmbyval, char elmalign,
+ bool *isNull)
+{
+ return array_get_element(PointerGetDatum(array), nSubscripts, indx,
+ arraytyplen, elmlen, elmbyval, elmalign,
+ isNull);
+}
+
+/*
+ * array_set : backwards compatibility wrapper for array_set_element
+ *
+ * This only works for detoasted/flattened varlena arrays, since the array
+ * argument and result are declared as "ArrayType *". However there's enough
+ * code like that to justify preserving this API.
+ */
+ArrayType *
+array_set(ArrayType *array, int nSubscripts, int *indx,
+ Datum dataValue, bool isNull,
+ int arraytyplen, int elmlen, bool elmbyval, char elmalign)
+{
+ return DatumGetArrayTypeP(array_set_element(PointerGetDatum(array),
+ nSubscripts, indx,
+ dataValue, isNull,
+ arraytyplen,
+ elmlen, elmbyval, elmalign));
+}
+
+/*
+ * array_map()
+ *
+ * Map an array through an arbitrary expression. Return a new array with
+ * the same dimensions and each source element transformed by the given,
+ * already-compiled expression. Each source element is placed in the
+ * innermost_caseval/innermost_casenull fields of the ExprState.
+ *
+ * Parameters are:
+ * * arrayd: Datum representing array argument.
+ * * exprstate: ExprState representing the per-element transformation.
+ * * econtext: context for expression evaluation.
+ * * retType: OID of element type of output array. This must be the same as,
+ * or binary-compatible with, the result type of the expression. It might
+ * be different from the input array's element type.
+ * * amstate: workspace for array_map. Must be zeroed by caller before
+ * first call, and not touched after that.
+ *
+ * It is legitimate to pass a freshly-zeroed ArrayMapState on each call,
+ * but better performance can be had if the state can be preserved across
+ * a series of calls.
+ *
+ * NB: caller must assure that input array is not NULL. NULL elements in
+ * the array are OK however.
+ * NB: caller should be running in econtext's per-tuple memory context.
+ */
+Datum
+array_map(Datum arrayd,
+ ExprState *exprstate, ExprContext *econtext,
+ Oid retType, ArrayMapState *amstate)
+{
+ AnyArrayType *v = DatumGetAnyArrayP(arrayd);
+ ArrayType *result;
+ Datum *values;
+ bool *nulls;
+ int *dim;
+ int ndim;
+ int nitems;
+ int i;
+ int32 nbytes = 0;
+ int32 dataoffset;
+ bool hasnulls;
+ Oid inpType;
+ int inp_typlen;
+ bool inp_typbyval;
+ char inp_typalign;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ array_iter iter;
+ ArrayMetaState *inp_extra;
+ ArrayMetaState *ret_extra;
+ Datum *transform_source = exprstate->innermost_caseval;
+ bool *transform_source_isnull = exprstate->innermost_casenull;
+
+ inpType = AARR_ELEMTYPE(v);
+ ndim = AARR_NDIM(v);
+ dim = AARR_DIMS(v);
+ nitems = ArrayGetNItems(ndim, dim);
+
+ /* Check for empty array */
+ if (nitems <= 0)
+ {
+ /* Return empty array */
+ return PointerGetDatum(construct_empty_array(retType));
+ }
+
+ /*
+ * We arrange to look up info about input and return element types only
+ * once per series of calls, assuming the element type doesn't change
+ * underneath us.
+ */
+ inp_extra = &amstate->inp_extra;
+ ret_extra = &amstate->ret_extra;
+
+ if (inp_extra->element_type != inpType)
+ {
+ get_typlenbyvalalign(inpType,
+ &inp_extra->typlen,
+ &inp_extra->typbyval,
+ &inp_extra->typalign);
+ inp_extra->element_type = inpType;
+ }
+ inp_typlen = inp_extra->typlen;
+ inp_typbyval = inp_extra->typbyval;
+ inp_typalign = inp_extra->typalign;
+
+ if (ret_extra->element_type != retType)
+ {
+ get_typlenbyvalalign(retType,
+ &ret_extra->typlen,
+ &ret_extra->typbyval,
+ &ret_extra->typalign);
+ ret_extra->element_type = retType;
+ }
+ typlen = ret_extra->typlen;
+ typbyval = ret_extra->typbyval;
+ typalign = ret_extra->typalign;
+
+ /* Allocate temporary arrays for new values */
+ values = (Datum *) palloc(nitems * sizeof(Datum));
+ nulls = (bool *) palloc(nitems * sizeof(bool));
+
+ /* Loop over source data */
+ array_iter_setup(&iter, v);
+ hasnulls = false;
+
+ for (i = 0; i < nitems; i++)
+ {
+ /* Get source element, checking for NULL */
+ *transform_source =
+ array_iter_next(&iter, transform_source_isnull, i,
+ inp_typlen, inp_typbyval, inp_typalign);
+
+ /* Apply the given expression to source element */
+ values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]);
+
+ if (nulls[i])
+ hasnulls = true;
+ else
+ {
+ /* Ensure data is not toasted */
+ if (typlen == -1)
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM(values[i]));
+ /* Update total result size */
+ nbytes = att_addlength_datum(nbytes, typlen, values[i]);
+ nbytes = att_align_nominal(nbytes, typalign);
+ /* check for overflow of total request */
+ if (!AllocSizeIsValid(nbytes))
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("array size exceeds the maximum allowed (%d)",
+ (int) MaxAllocSize)));
+ }
+ }
+
+ /* Allocate and fill the result array */
+ if (hasnulls)
+ {
+ dataoffset = ARR_OVERHEAD_WITHNULLS(ndim, nitems);
+ nbytes += dataoffset;
+ }
+ else
+ {
+ dataoffset = 0; /* marker for no null bitmap */
+ nbytes += ARR_OVERHEAD_NONULLS(ndim);
+ }
+ result = (ArrayType *) palloc0(nbytes);
+ SET_VARSIZE(result, nbytes);
+ result->ndim = ndim;
+ result->dataoffset = dataoffset;
+ result->elemtype = retType;
+ memcpy(ARR_DIMS(result), AARR_DIMS(v), ndim * sizeof(int));
+ memcpy(ARR_LBOUND(result), AARR_LBOUND(v), ndim * sizeof(int));
+
+ CopyArrayEls(result,
+ values, nulls, nitems,
+ typlen, typbyval, typalign,
+ false);
+
+ /*
+ * Note: do not risk trying to pfree the results of the called expression
+ */
+ pfree(values);
+ pfree(nulls);
+
+ return PointerGetDatum(result);
+}
+
+/*
+ * construct_array --- simple method for constructing an array object
+ *
+ * elems: array of Datum items to become the array contents
+ * (NULL element values are not supported).
+ * nelems: number of items
+ * elmtype, elmlen, elmbyval, elmalign: info for the datatype of the items
+ *
+ * A palloc'd 1-D array object is constructed and returned. Note that
+ * elem values will be copied into the object even if pass-by-ref type.
+ * Also note the result will be 0-D not 1-D if nelems = 0.
+ *
+ * NOTE: it would be cleaner to look up the elmlen/elmbval/elmalign info
+ * from the system catalogs, given the elmtype. However, the caller is
+ * in a better position to cache this info across multiple uses, or even
+ * to hard-wire values if the element type is hard-wired.
+ */
+ArrayType *
+construct_array(Datum *elems, int nelems,
+ Oid elmtype,
+ int elmlen, bool elmbyval, char elmalign)
+{
+ int dims[1];
+ int lbs[1];
+
+ dims[0] = nelems;
+ lbs[0] = 1;
+
+ return construct_md_array(elems, NULL, 1, dims, lbs,
+ elmtype, elmlen, elmbyval, elmalign);
+}
+
+/*
+ * construct_md_array --- simple method for constructing an array object
+ * with arbitrary dimensions and possible NULLs
+ *
+ * elems: array of Datum items to become the array contents
+ * nulls: array of is-null flags (can be NULL if no nulls)
+ * ndims: number of dimensions
+ * dims: integer array with size of each dimension
+ * lbs: integer array with lower bound of each dimension
+ * elmtype, elmlen, elmbyval, elmalign: info for the datatype of the items
+ *
+ * A palloc'd ndims-D array object is constructed and returned. Note that
+ * elem values will be copied into the object even if pass-by-ref type.
+ * Also note the result will be 0-D not ndims-D if any dims[i] = 0.
+ *
+ * NOTE: it would be cleaner to look up the elmlen/elmbval/elmalign info
+ * from the system catalogs, given the elmtype. However, the caller is
+ * in a better position to cache this info across multiple uses, or even
+ * to hard-wire values if the element type is hard-wired.
+ */
+ArrayType *
+construct_md_array(Datum *elems,
+ bool *nulls,
+ int ndims,
+ int *dims,
+ int *lbs,
+ Oid elmtype, int elmlen, bool elmbyval, char elmalign)
+{
+ ArrayType *result;
+ bool hasnulls;
+ int32 nbytes;
+ int32 dataoffset;
+ int i;
+ int nelems;
+
+ if (ndims < 0) /* we do allow zero-dimension arrays */
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid number of dimensions: %d", ndims)));
+ if (ndims > MAXDIM)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
+ ndims, MAXDIM)));
+
+ /* This checks for overflow of the array dimensions */
+ nelems = ArrayGetNItems(ndims, dims);
+ ArrayCheckBounds(ndims, dims, lbs);
+
+ /* if ndims <= 0 or any dims[i] == 0, return empty array */
+ if (nelems <= 0)
+ return construct_empty_array(elmtype);
+
+ /* compute required space */
+ nbytes = 0;
+ hasnulls = false;
+ for (i = 0; i < nelems; i++)
+ {
+ if (nulls && nulls[i])
+ {
+ hasnulls = true;
+ continue;
+ }
+ /* make sure data is not toasted */
+ if (elmlen == -1)
+ elems[i] = PointerGetDatum(PG_DETOAST_DATUM(elems[i]));
+ nbytes = att_addlength_datum(nbytes, elmlen, elems[i]);
+ nbytes = att_align_nominal(nbytes, elmalign);
+ /* check for overflow of total request */
+ if (!AllocSizeIsValid(nbytes))
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("array size exceeds the maximum allowed (%d)",
+ (int) MaxAllocSize)));
+ }
+
+ /* Allocate and initialize result array */
+ if (hasnulls)
+ {
+ dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nelems);
+ nbytes += dataoffset;
+ }
+ else
+ {
+ dataoffset = 0; /* marker for no null bitmap */
+ nbytes += ARR_OVERHEAD_NONULLS(ndims);
+ }
+ result = (ArrayType *) palloc0(nbytes);
+ SET_VARSIZE(result, nbytes);
+ result->ndim = ndims;
+ result->dataoffset = dataoffset;
+ result->elemtype = elmtype;
+ memcpy(ARR_DIMS(result), dims, ndims * sizeof(int));
+ memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int));
+
+ CopyArrayEls(result,
+ elems, nulls, nelems,
+ elmlen, elmbyval, elmalign,
+ false);
+
+ return result;
+}
+
+/*
+ * construct_empty_array --- make a zero-dimensional array of given type
+ */
+ArrayType *
+construct_empty_array(Oid elmtype)
+{
+ ArrayType *result;
+
+ result = (ArrayType *) palloc0(sizeof(ArrayType));
+ SET_VARSIZE(result, sizeof(ArrayType));
+ result->ndim = 0;
+ result->dataoffset = 0;
+ result->elemtype = elmtype;
+ return result;
+}
+
+/*
+ * construct_empty_expanded_array: make an empty expanded array
+ * given only type information. (metacache can be NULL if not needed.)
+ */
+ExpandedArrayHeader *
+construct_empty_expanded_array(Oid element_type,
+ MemoryContext parentcontext,
+ ArrayMetaState *metacache)
+{
+ ArrayType *array = construct_empty_array(element_type);
+ Datum d;
+
+ d = expand_array(PointerGetDatum(array), parentcontext, metacache);
+ pfree(array);
+ return (ExpandedArrayHeader *) DatumGetEOHP(d);
+}
+
+/*
+ * deconstruct_array --- simple method for extracting data from an array
+ *
+ * array: array object to examine (must not be NULL)
+ * elmtype, elmlen, elmbyval, elmalign: info for the datatype of the items
+ * elemsp: return value, set to point to palloc'd array of Datum values
+ * nullsp: return value, set to point to palloc'd array of isnull markers
+ * nelemsp: return value, set to number of extracted values
+ *
+ * The caller may pass nullsp == NULL if it does not support NULLs in the
+ * array. Note that this produces a very uninformative error message,
+ * so do it only in cases where a NULL is really not expected.
+ *
+ * If array elements are pass-by-ref data type, the returned Datums will
+ * be pointers into the array object.
+ *
+ * NOTE: it would be cleaner to look up the elmlen/elmbval/elmalign info
+ * from the system catalogs, given the elmtype. However, in most current
+ * uses the type is hard-wired into the caller and so we can save a lookup
+ * cycle by hard-wiring the type info as well.
+ */
+void
+deconstruct_array(ArrayType *array,
+ Oid elmtype,
+ int elmlen, bool elmbyval, char elmalign,
+ Datum **elemsp, bool **nullsp, int *nelemsp)
+{
+ Datum *elems;
+ bool *nulls;
+ int nelems;
+ char *p;
+ bits8 *bitmap;
+ int bitmask;
+ int i;
+
+ Assert(ARR_ELEMTYPE(array) == elmtype);
+
+ nelems = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array));
+ *elemsp = elems = (Datum *) palloc(nelems * sizeof(Datum));
+ if (nullsp)
+ *nullsp = nulls = (bool *) palloc0(nelems * sizeof(bool));
+ else
+ nulls = NULL;
+ *nelemsp = nelems;
+
+ p = ARR_DATA_PTR(array);
+ bitmap = ARR_NULLBITMAP(array);
+ bitmask = 1;
+
+ for (i = 0; i < nelems; i++)
+ {
+ /* Get source element, checking for NULL */
+ if (bitmap && (*bitmap & bitmask) == 0)
+ {
+ elems[i] = (Datum) 0;
+ if (nulls)
+ nulls[i] = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("null array element not allowed in this context")));
+ }
+ else
+ {
+ elems[i] = fetch_att(p, elmbyval, elmlen);
+ p = att_addlength_pointer(p, elmlen, p);
+ p = (char *) att_align_nominal(p, elmalign);
+ }
+
+ /* advance bitmap pointer if any */
+ if (bitmap)
+ {
+ bitmask <<= 1;
+ if (bitmask == 0x100)
+ {
+ bitmap++;
+ bitmask = 1;
+ }
+ }
+ }
+}
+
+/*
+ * array_contains_nulls --- detect whether an array has any null elements
+ *
+ * This gives an accurate answer, whereas testing ARR_HASNULL only tells
+ * if the array *might* contain a null.
+ */
+bool
+array_contains_nulls(ArrayType *array)
+{
+ int nelems;
+ bits8 *bitmap;
+ int bitmask;
+
+ /* Easy answer if there's no null bitmap */
+ if (!ARR_HASNULL(array))
+ return false;
+
+ nelems = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array));
+
+ bitmap = ARR_NULLBITMAP(array);
+
+ /* check whole bytes of the bitmap byte-at-a-time */
+ while (nelems >= 8)
+ {
+ if (*bitmap != 0xFF)
+ return true;
+ bitmap++;
+ nelems -= 8;
+ }
+
+ /* check last partial byte */
+ bitmask = 1;
+ while (nelems > 0)
+ {
+ if ((*bitmap & bitmask) == 0)
+ return true;
+ bitmask <<= 1;
+ nelems--;
+ }
+
+ return false;
+}
+
+
+/*
+ * array_eq :
+ * compares two arrays for equality
+ * result :
+ * returns true if the arrays are equal, false otherwise.
+ *
+ * Note: we do not use array_cmp here, since equality may be meaningful in
+ * datatypes that don't have a total ordering (and hence no btree support).
+ */
+Datum
+array_eq(PG_FUNCTION_ARGS)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ AnyArrayType *array1 = PG_GETARG_ANY_ARRAY_P(0);
+ AnyArrayType *array2 = PG_GETARG_ANY_ARRAY_P(1);
+ Oid collation = PG_GET_COLLATION();
+ int ndims1 = AARR_NDIM(array1);
+ int ndims2 = AARR_NDIM(array2);
+ int *dims1 = AARR_DIMS(array1);
+ int *dims2 = AARR_DIMS(array2);
+ int *lbs1 = AARR_LBOUND(array1);
+ int *lbs2 = AARR_LBOUND(array2);
+ Oid element_type = AARR_ELEMTYPE(array1);
+ bool result = true;
+ int nitems;
+ TypeCacheEntry *typentry;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ array_iter it1;
+ array_iter it2;
+ int i;
+
+ if (element_type != AARR_ELEMTYPE(array2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays of different element types")));
+
+ /* fast path if the arrays do not have the same dimensionality */
+ if (ndims1 != ndims2 ||
+ memcmp(dims1, dims2, ndims1 * sizeof(int)) != 0 ||
+ memcmp(lbs1, lbs2, ndims1 * sizeof(int)) != 0)
+ result = false;
+ else
+ {
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used
+ * as an index support function.
+ */
+ typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != element_type)
+ {
+ 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))));
+ fcinfo->flinfo->fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * apply the operator to each pair of array elements.
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nitems = ArrayGetNItems(ndims1, dims1);
+ array_iter_setup(&it1, array1);
+ array_iter_setup(&it2, array2);
+
+ for (i = 0; i < nitems; i++)
+ {
+ Datum elt1;
+ Datum elt2;
+ bool isnull1;
+ bool isnull2;
+ bool oprresult;
+
+ /* Get elements, checking for NULL */
+ elt1 = array_iter_next(&it1, &isnull1, i,
+ typlen, typbyval, typalign);
+ elt2 = array_iter_next(&it2, &isnull2, i,
+ typlen, typbyval, typalign);
+
+ /*
+ * We consider two NULLs equal; NULL and not-NULL are unequal.
+ */
+ if (isnull1 && isnull2)
+ continue;
+ if (isnull1 || isnull2)
+ {
+ result = false;
+ break;
+ }
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt1;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elt2;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (locfcinfo->isnull || !oprresult)
+ {
+ result = false;
+ break;
+ }
+ }
+ }
+
+ /* Avoid leaking memory when handed toasted input. */
+ AARR_FREE_IF_COPY(array1, 0);
+ AARR_FREE_IF_COPY(array2, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
+
+/*-----------------------------------------------------------------------------
+ * array-array bool operators:
+ * Given two arrays, iterate comparison operators
+ * over the array. Uses logic similar to text comparison
+ * functions, except element-by-element instead of
+ * character-by-character.
+ *----------------------------------------------------------------------------
+ */
+
+Datum
+array_ne(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_BOOL(!DatumGetBool(array_eq(fcinfo)));
+}
+
+Datum
+array_lt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_BOOL(array_cmp(fcinfo) < 0);
+}
+
+Datum
+array_gt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_BOOL(array_cmp(fcinfo) > 0);
+}
+
+Datum
+array_le(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_BOOL(array_cmp(fcinfo) <= 0);
+}
+
+Datum
+array_ge(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_BOOL(array_cmp(fcinfo) >= 0);
+}
+
+Datum
+btarraycmp(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT32(array_cmp(fcinfo));
+}
+
+/*
+ * array_cmp()
+ * Internal comparison function for arrays.
+ *
+ * Returns -1, 0 or 1
+ */
+static int
+array_cmp(FunctionCallInfo fcinfo)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ AnyArrayType *array1 = PG_GETARG_ANY_ARRAY_P(0);
+ AnyArrayType *array2 = PG_GETARG_ANY_ARRAY_P(1);
+ Oid collation = PG_GET_COLLATION();
+ int ndims1 = AARR_NDIM(array1);
+ int ndims2 = AARR_NDIM(array2);
+ int *dims1 = AARR_DIMS(array1);
+ int *dims2 = AARR_DIMS(array2);
+ int nitems1 = ArrayGetNItems(ndims1, dims1);
+ int nitems2 = ArrayGetNItems(ndims2, dims2);
+ Oid element_type = AARR_ELEMTYPE(array1);
+ int result = 0;
+ TypeCacheEntry *typentry;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int min_nitems;
+ array_iter it1;
+ array_iter it2;
+ int i;
+
+ if (element_type != AARR_ELEMTYPE(array2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays of different element types")));
+
+ /*
+ * We arrange to look up the comparison function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != element_type)
+ {
+ typentry = lookup_type_cache(element_type,
+ TYPECACHE_CMP_PROC_FINFO);
+ if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify a comparison function for type %s",
+ format_type_be(element_type))));
+ fcinfo->flinfo->fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * apply the operator to each pair of array elements.
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->cmp_proc_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ min_nitems = Min(nitems1, nitems2);
+ array_iter_setup(&it1, array1);
+ array_iter_setup(&it2, array2);
+
+ for (i = 0; i < min_nitems; i++)
+ {
+ Datum elt1;
+ Datum elt2;
+ bool isnull1;
+ bool isnull2;
+ int32 cmpresult;
+
+ /* Get elements, checking for NULL */
+ elt1 = array_iter_next(&it1, &isnull1, i, typlen, typbyval, typalign);
+ elt2 = array_iter_next(&it2, &isnull2, i, typlen, typbyval, typalign);
+
+ /*
+ * We consider two NULLs equal; NULL > not-NULL.
+ */
+ if (isnull1 && isnull2)
+ continue;
+ if (isnull1)
+ {
+ /* arg1 is greater than arg2 */
+ result = 1;
+ break;
+ }
+ if (isnull2)
+ {
+ /* arg1 is less than arg2 */
+ result = -1;
+ break;
+ }
+
+ /* Compare the pair of elements */
+ locfcinfo->args[0].value = elt1;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elt2;
+ locfcinfo->args[1].isnull = false;
+ cmpresult = DatumGetInt32(FunctionCallInvoke(locfcinfo));
+
+ /* We don't expect comparison support functions to return null */
+ Assert(!locfcinfo->isnull);
+
+ if (cmpresult == 0)
+ continue; /* equal */
+
+ if (cmpresult < 0)
+ {
+ /* arg1 is less than arg2 */
+ result = -1;
+ break;
+ }
+ else
+ {
+ /* arg1 is greater than arg2 */
+ result = 1;
+ break;
+ }
+ }
+
+ /*
+ * If arrays contain same data (up to end of shorter one), apply
+ * additional rules to sort by dimensionality. The relative significance
+ * of the different bits of information is historical; mainly we just care
+ * that we don't say "equal" for arrays of different dimensionality.
+ */
+ if (result == 0)
+ {
+ if (nitems1 != nitems2)
+ result = (nitems1 < nitems2) ? -1 : 1;
+ else if (ndims1 != ndims2)
+ result = (ndims1 < ndims2) ? -1 : 1;
+ else
+ {
+ for (i = 0; i < ndims1; i++)
+ {
+ if (dims1[i] != dims2[i])
+ {
+ result = (dims1[i] < dims2[i]) ? -1 : 1;
+ break;
+ }
+ }
+ if (result == 0)
+ {
+ int *lbound1 = AARR_LBOUND(array1);
+ int *lbound2 = AARR_LBOUND(array2);
+
+ for (i = 0; i < ndims1; i++)
+ {
+ if (lbound1[i] != lbound2[i])
+ {
+ result = (lbound1[i] < lbound2[i]) ? -1 : 1;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /* Avoid leaking memory when handed toasted input. */
+ AARR_FREE_IF_COPY(array1, 0);
+ AARR_FREE_IF_COPY(array2, 1);
+
+ return result;
+}
+
+
+/*-----------------------------------------------------------------------------
+ * array hashing
+ * Hash the elements and combine the results.
+ *----------------------------------------------------------------------------
+ */
+
+Datum
+hash_array(PG_FUNCTION_ARGS)
+{
+ LOCAL_FCINFO(locfcinfo, 1);
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ int ndims = AARR_NDIM(array);
+ int *dims = AARR_DIMS(array);
+ Oid element_type = AARR_ELEMTYPE(array);
+ uint32 result = 1;
+ int nitems;
+ TypeCacheEntry *typentry;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter iter;
+
+ /*
+ * We arrange to look up the hash function only once per series of calls,
+ * assuming the element type doesn't change underneath us. The typcache
+ * is used so that we have no memory leakage when being used as an index
+ * support function.
+ */
+ typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != element_type)
+ {
+ typentry = lookup_type_cache(element_type,
+ TYPECACHE_HASH_PROC_FINFO);
+ if (!OidIsValid(typentry->hash_proc_finfo.fn_oid) && element_type != RECORDOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify a hash function for type %s",
+ format_type_be(element_type))));
+
+ /*
+ * The type cache doesn't believe that record is hashable (see
+ * cache_record_field_properties()), but since we're here, we're
+ * committed to hashing, so we can assume it does. Worst case, if any
+ * components of the record don't support hashing, we will fail at
+ * execution.
+ */
+ if (element_type == RECORDOID)
+ {
+ MemoryContext oldcontext;
+ TypeCacheEntry *record_typentry;
+
+ oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
+
+ /*
+ * Make fake type cache entry structure. Note that we can't just
+ * modify typentry, since that points directly into the type cache.
+ */
+ record_typentry = palloc0(sizeof(*record_typentry));
+ record_typentry->type_id = element_type;
+
+ /* fill in what we need below */
+ record_typentry->typlen = typentry->typlen;
+ record_typentry->typbyval = typentry->typbyval;
+ record_typentry->typalign = typentry->typalign;
+ fmgr_info(F_HASH_RECORD, &record_typentry->hash_proc_finfo);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ typentry = record_typentry;
+ }
+
+ fcinfo->flinfo->fn_extra = (void *) typentry;
+ }
+
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * apply the hash function to each array element.
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->hash_proc_finfo, 1,
+ PG_GET_COLLATION(), NULL, NULL);
+
+ /* Loop over source data */
+ nitems = ArrayGetNItems(ndims, dims);
+ array_iter_setup(&iter, array);
+
+ for (i = 0; i < nitems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ uint32 elthash;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&iter, &isnull, i, typlen, typbyval, typalign);
+
+ if (isnull)
+ {
+ /* Treat nulls as having hashvalue 0 */
+ elthash = 0;
+ }
+ else
+ {
+ /* Apply the hash function */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ elthash = DatumGetUInt32(FunctionCallInvoke(locfcinfo));
+ /* We don't expect hash functions to return null */
+ Assert(!locfcinfo->isnull);
+ }
+
+ /*
+ * Combine hash values of successive elements by multiplying the
+ * current value by 31 and adding on the new element's hash value.
+ *
+ * The result is a sum in which each element's hash value is
+ * multiplied by a different power of 31. This is modulo 2^32
+ * arithmetic, and the powers of 31 modulo 2^32 form a cyclic group of
+ * order 2^27. So for arrays of up to 2^27 elements, each element's
+ * hash value is multiplied by a different (odd) number, resulting in
+ * a good mixing of all the elements' hash values.
+ */
+ result = (result << 5) - result + elthash;
+ }
+
+ /* Avoid leaking memory when handed toasted input. */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_UINT32(result);
+}
+
+/*
+ * Returns 64-bit value by hashing a value to a 64-bit value, with a seed.
+ * Otherwise, similar to hash_array.
+ */
+Datum
+hash_array_extended(PG_FUNCTION_ARGS)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ uint64 seed = PG_GETARG_INT64(1);
+ int ndims = AARR_NDIM(array);
+ int *dims = AARR_DIMS(array);
+ Oid element_type = AARR_ELEMTYPE(array);
+ uint64 result = 1;
+ int nitems;
+ TypeCacheEntry *typentry;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter iter;
+
+ typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != element_type)
+ {
+ typentry = lookup_type_cache(element_type,
+ TYPECACHE_HASH_EXTENDED_PROC_FINFO);
+ if (!OidIsValid(typentry->hash_extended_proc_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an extended hash function for type %s",
+ format_type_be(element_type))));
+ fcinfo->flinfo->fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ InitFunctionCallInfoData(*locfcinfo, &typentry->hash_extended_proc_finfo, 2,
+ PG_GET_COLLATION(), NULL, NULL);
+
+ /* Loop over source data */
+ nitems = ArrayGetNItems(ndims, dims);
+ array_iter_setup(&iter, array);
+
+ for (i = 0; i < nitems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ uint64 elthash;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&iter, &isnull, i, typlen, typbyval, typalign);
+
+ if (isnull)
+ {
+ elthash = 0;
+ }
+ else
+ {
+ /* Apply the hash function */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = Int64GetDatum(seed);
+ locfcinfo->args[1].isnull = false;
+ elthash = DatumGetUInt64(FunctionCallInvoke(locfcinfo));
+ /* We don't expect hash functions to return null */
+ Assert(!locfcinfo->isnull);
+ }
+
+ result = (result << 5) - result + elthash;
+ }
+
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_UINT64(result);
+}
+
+
+/*-----------------------------------------------------------------------------
+ * array overlap/containment comparisons
+ * These use the same methods of comparing array elements as array_eq.
+ * We consider only the elements of the arrays, ignoring dimensionality.
+ *----------------------------------------------------------------------------
+ */
+
+/*
+ * array_contain_compare :
+ * compares two arrays for overlap/containment
+ *
+ * When matchall is true, return true if all members of array1 are in array2.
+ * When matchall is false, return true if any members of array1 are in array2.
+ */
+static bool
+array_contain_compare(AnyArrayType *array1, AnyArrayType *array2, Oid collation,
+ bool matchall, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ bool result = matchall;
+ Oid element_type = AARR_ELEMTYPE(array1);
+ TypeCacheEntry *typentry;
+ int nelems1;
+ Datum *values2;
+ bool *nulls2;
+ int nelems2;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ int j;
+ array_iter it1;
+
+ if (element_type != AARR_ELEMTYPE(array2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays of different element types")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != element_type)
+ {
+ 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))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Since we probably will need to scan array2 multiple times, it's
+ * worthwhile to use deconstruct_array on it. We scan array1 the hard way
+ * however, since we very likely won't need to look at all of it.
+ */
+ if (VARATT_IS_EXPANDED_HEADER(array2))
+ {
+ /* This should be safe even if input is read-only */
+ deconstruct_expanded_array(&(array2->xpn));
+ values2 = array2->xpn.dvalues;
+ nulls2 = array2->xpn.dnulls;
+ nelems2 = array2->xpn.nelems;
+ }
+ else
+ deconstruct_array((ArrayType *) array2,
+ element_type, typlen, typbyval, typalign,
+ &values2, &nulls2, &nelems2);
+
+ /*
+ * Apply the comparison operator to each pair of array elements.
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems1 = ArrayGetNItems(AARR_NDIM(array1), AARR_DIMS(array1));
+ array_iter_setup(&it1, array1);
+
+ for (i = 0; i < nelems1; i++)
+ {
+ Datum elt1;
+ bool isnull1;
+
+ /* Get element, checking for NULL */
+ elt1 = array_iter_next(&it1, &isnull1, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. XXX this diverges from the "NULL=NULL" behavior of
+ * array_eq, should we act like that?
+ */
+ if (isnull1)
+ {
+ if (matchall)
+ {
+ result = false;
+ break;
+ }
+ continue;
+ }
+
+ for (j = 0; j < nelems2; j++)
+ {
+ Datum elt2 = values2[j];
+ bool isnull2 = nulls2 ? nulls2[j] : false;
+ bool oprresult;
+
+ if (isnull2)
+ continue; /* can't match */
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt1;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elt2;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ break;
+ }
+
+ if (j < nelems2)
+ {
+ /* found a match for elt1 */
+ if (!matchall)
+ {
+ result = true;
+ break;
+ }
+ }
+ else
+ {
+ /* no match for elt1 */
+ if (matchall)
+ {
+ result = false;
+ break;
+ }
+ }
+ }
+
+ return result;
+}
+
+Datum
+arrayoverlap(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array1 = PG_GETARG_ANY_ARRAY_P(0);
+ AnyArrayType *array2 = PG_GETARG_ANY_ARRAY_P(1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ result = array_contain_compare(array1, array2, collation, false,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input. */
+ AARR_FREE_IF_COPY(array1, 0);
+ AARR_FREE_IF_COPY(array2, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arraycontains(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array1 = PG_GETARG_ANY_ARRAY_P(0);
+ AnyArrayType *array2 = PG_GETARG_ANY_ARRAY_P(1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ result = array_contain_compare(array2, array1, collation, true,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input. */
+ AARR_FREE_IF_COPY(array1, 0);
+ AARR_FREE_IF_COPY(array2, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arraycontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array1 = PG_GETARG_ANY_ARRAY_P(0);
+ AnyArrayType *array2 = PG_GETARG_ANY_ARRAY_P(1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ result = array_contain_compare(array1, array2, collation, true,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input. */
+ AARR_FREE_IF_COPY(array1, 0);
+ AARR_FREE_IF_COPY(array2, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
+
+/*-----------------------------------------------------------------------------
+ * Array iteration functions
+ * These functions are used to iterate efficiently through arrays
+ *-----------------------------------------------------------------------------
+ */
+
+/*
+ * array_create_iterator --- set up to iterate through an array
+ *
+ * If slice_ndim is zero, we will iterate element-by-element; the returned
+ * datums are of the array's element type.
+ *
+ * If slice_ndim is 1..ARR_NDIM(arr), we will iterate by slices: the
+ * returned datums are of the same array type as 'arr', but of size
+ * equal to the rightmost N dimensions of 'arr'.
+ *
+ * The passed-in array must remain valid for the lifetime of the iterator.
+ */
+ArrayIterator
+array_create_iterator(ArrayType *arr, int slice_ndim, ArrayMetaState *mstate)
+{
+ ArrayIterator iterator = palloc0(sizeof(ArrayIteratorData));
+
+ /*
+ * Sanity-check inputs --- caller should have got this right already
+ */
+ Assert(PointerIsValid(arr));
+ if (slice_ndim < 0 || slice_ndim > ARR_NDIM(arr))
+ elog(ERROR, "invalid arguments to array_create_iterator");
+
+ /*
+ * Remember basic info about the array and its element type
+ */
+ iterator->arr = arr;
+ iterator->nullbitmap = ARR_NULLBITMAP(arr);
+ iterator->nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+
+ if (mstate != NULL)
+ {
+ Assert(mstate->element_type == ARR_ELEMTYPE(arr));
+
+ iterator->typlen = mstate->typlen;
+ iterator->typbyval = mstate->typbyval;
+ iterator->typalign = mstate->typalign;
+ }
+ else
+ get_typlenbyvalalign(ARR_ELEMTYPE(arr),
+ &iterator->typlen,
+ &iterator->typbyval,
+ &iterator->typalign);
+
+ /*
+ * Remember the slicing parameters.
+ */
+ iterator->slice_ndim = slice_ndim;
+
+ if (slice_ndim > 0)
+ {
+ /*
+ * Get pointers into the array's dims and lbound arrays to represent
+ * the dims/lbound arrays of a slice. These are the same as the
+ * rightmost N dimensions of the array.
+ */
+ iterator->slice_dims = ARR_DIMS(arr) + ARR_NDIM(arr) - slice_ndim;
+ iterator->slice_lbound = ARR_LBOUND(arr) + ARR_NDIM(arr) - slice_ndim;
+
+ /*
+ * Compute number of elements in a slice.
+ */
+ iterator->slice_len = ArrayGetNItems(slice_ndim,
+ iterator->slice_dims);
+
+ /*
+ * Create workspace for building sub-arrays.
+ */
+ iterator->slice_values = (Datum *)
+ palloc(iterator->slice_len * sizeof(Datum));
+ iterator->slice_nulls = (bool *)
+ palloc(iterator->slice_len * sizeof(bool));
+ }
+
+ /*
+ * Initialize our data pointer and linear element number. These will
+ * advance through the array during array_iterate().
+ */
+ iterator->data_ptr = ARR_DATA_PTR(arr);
+ iterator->current_item = 0;
+
+ return iterator;
+}
+
+/*
+ * Iterate through the array referenced by 'iterator'.
+ *
+ * As long as there is another element (or slice), return it into
+ * *value / *isnull, and return true. Return false when no more data.
+ */
+bool
+array_iterate(ArrayIterator iterator, Datum *value, bool *isnull)
+{
+ /* Done if we have reached the end of the array */
+ if (iterator->current_item >= iterator->nitems)
+ return false;
+
+ if (iterator->slice_ndim == 0)
+ {
+ /*
+ * Scalar case: return one element.
+ */
+ if (array_get_isnull(iterator->nullbitmap, iterator->current_item++))
+ {
+ *isnull = true;
+ *value = (Datum) 0;
+ }
+ else
+ {
+ /* non-NULL, so fetch the individual Datum to return */
+ char *p = iterator->data_ptr;
+
+ *isnull = false;
+ *value = fetch_att(p, iterator->typbyval, iterator->typlen);
+
+ /* Move our data pointer forward to the next element */
+ p = att_addlength_pointer(p, iterator->typlen, p);
+ p = (char *) att_align_nominal(p, iterator->typalign);
+ iterator->data_ptr = p;
+ }
+ }
+ else
+ {
+ /*
+ * Slice case: build and return an array of the requested size.
+ */
+ ArrayType *result;
+ Datum *values = iterator->slice_values;
+ bool *nulls = iterator->slice_nulls;
+ char *p = iterator->data_ptr;
+ int i;
+
+ for (i = 0; i < iterator->slice_len; i++)
+ {
+ if (array_get_isnull(iterator->nullbitmap,
+ iterator->current_item++))
+ {
+ nulls[i] = true;
+ values[i] = (Datum) 0;
+ }
+ else
+ {
+ nulls[i] = false;
+ values[i] = fetch_att(p, iterator->typbyval, iterator->typlen);
+
+ /* Move our data pointer forward to the next element */
+ p = att_addlength_pointer(p, iterator->typlen, p);
+ p = (char *) att_align_nominal(p, iterator->typalign);
+ }
+ }
+
+ iterator->data_ptr = p;
+
+ result = construct_md_array(values,
+ nulls,
+ iterator->slice_ndim,
+ iterator->slice_dims,
+ iterator->slice_lbound,
+ ARR_ELEMTYPE(iterator->arr),
+ iterator->typlen,
+ iterator->typbyval,
+ iterator->typalign);
+
+ *isnull = false;
+ *value = PointerGetDatum(result);
+ }
+
+ return true;
+}
+
+/*
+ * Release an ArrayIterator data structure
+ */
+void
+array_free_iterator(ArrayIterator iterator)
+{
+ if (iterator->slice_ndim > 0)
+ {
+ pfree(iterator->slice_values);
+ pfree(iterator->slice_nulls);
+ }
+ pfree(iterator);
+}
+
+
+/***************************************************************************/
+/******************| Support Routines |*****************/
+/***************************************************************************/
+
+/*
+ * Check whether a specific array element is NULL
+ *
+ * nullbitmap: pointer to array's null bitmap (NULL if none)
+ * offset: 0-based linear element number of array element
+ */
+static bool
+array_get_isnull(const bits8 *nullbitmap, int offset)
+{
+ if (nullbitmap == NULL)
+ return false; /* assume not null */
+ if (nullbitmap[offset / 8] & (1 << (offset % 8)))
+ return false; /* not null */
+ return true;
+}
+
+/*
+ * Set a specific array element's null-bitmap entry
+ *
+ * nullbitmap: pointer to array's null bitmap (mustn't be NULL)
+ * offset: 0-based linear element number of array element
+ * isNull: null status to set
+ */
+static void
+array_set_isnull(bits8 *nullbitmap, int offset, bool isNull)
+{
+ int bitmask;
+
+ nullbitmap += offset / 8;
+ bitmask = 1 << (offset % 8);
+ if (isNull)
+ *nullbitmap &= ~bitmask;
+ else
+ *nullbitmap |= bitmask;
+}
+
+/*
+ * Fetch array element at pointer, converted correctly to a Datum
+ *
+ * Caller must have handled case of NULL element
+ */
+static Datum
+ArrayCast(char *value, bool byval, int len)
+{
+ return fetch_att(value, byval, len);
+}
+
+/*
+ * Copy datum to *dest and return total space used (including align padding)
+ *
+ * Caller must have handled case of NULL element
+ */
+static int
+ArrayCastAndSet(Datum src,
+ int typlen,
+ bool typbyval,
+ char typalign,
+ char *dest)
+{
+ int inc;
+
+ if (typlen > 0)
+ {
+ if (typbyval)
+ store_att_byval(dest, src, typlen);
+ else
+ memmove(dest, DatumGetPointer(src), typlen);
+ inc = att_align_nominal(typlen, typalign);
+ }
+ else
+ {
+ Assert(!typbyval);
+ inc = att_addlength_datum(0, typlen, src);
+ memmove(dest, DatumGetPointer(src), inc);
+ inc = att_align_nominal(inc, typalign);
+ }
+
+ return inc;
+}
+
+/*
+ * Advance ptr over nitems array elements
+ *
+ * ptr: starting location in array
+ * offset: 0-based linear element number of first element (the one at *ptr)
+ * nullbitmap: start of array's null bitmap, or NULL if none
+ * nitems: number of array elements to advance over (>= 0)
+ * typlen, typbyval, typalign: storage parameters of array element datatype
+ *
+ * It is caller's responsibility to ensure that nitems is within range
+ */
+static char *
+array_seek(char *ptr, int offset, bits8 *nullbitmap, int nitems,
+ int typlen, bool typbyval, char typalign)
+{
+ int bitmask;
+ int i;
+
+ /* easy if fixed-size elements and no NULLs */
+ if (typlen > 0 && !nullbitmap)
+ return ptr + nitems * ((Size) att_align_nominal(typlen, typalign));
+
+ /* seems worth having separate loops for NULL and no-NULLs cases */
+ if (nullbitmap)
+ {
+ nullbitmap += offset / 8;
+ bitmask = 1 << (offset % 8);
+
+ for (i = 0; i < nitems; i++)
+ {
+ if (*nullbitmap & bitmask)
+ {
+ ptr = att_addlength_pointer(ptr, typlen, ptr);
+ ptr = (char *) att_align_nominal(ptr, typalign);
+ }
+ bitmask <<= 1;
+ if (bitmask == 0x100)
+ {
+ nullbitmap++;
+ bitmask = 1;
+ }
+ }
+ }
+ else
+ {
+ for (i = 0; i < nitems; i++)
+ {
+ ptr = att_addlength_pointer(ptr, typlen, ptr);
+ ptr = (char *) att_align_nominal(ptr, typalign);
+ }
+ }
+ return ptr;
+}
+
+/*
+ * Compute total size of the nitems array elements starting at *ptr
+ *
+ * Parameters same as for array_seek
+ */
+static int
+array_nelems_size(char *ptr, int offset, bits8 *nullbitmap, int nitems,
+ int typlen, bool typbyval, char typalign)
+{
+ return array_seek(ptr, offset, nullbitmap, nitems,
+ typlen, typbyval, typalign) - ptr;
+}
+
+/*
+ * Copy nitems array elements from srcptr to destptr
+ *
+ * destptr: starting destination location (must be enough room!)
+ * nitems: number of array elements to copy (>= 0)
+ * srcptr: starting location in source array
+ * offset: 0-based linear element number of first element (the one at *srcptr)
+ * nullbitmap: start of source array's null bitmap, or NULL if none
+ * typlen, typbyval, typalign: storage parameters of array element datatype
+ *
+ * Returns number of bytes copied
+ *
+ * NB: this does not take care of setting up the destination's null bitmap!
+ */
+static int
+array_copy(char *destptr, int nitems,
+ char *srcptr, int offset, bits8 *nullbitmap,
+ int typlen, bool typbyval, char typalign)
+{
+ int numbytes;
+
+ numbytes = array_nelems_size(srcptr, offset, nullbitmap, nitems,
+ typlen, typbyval, typalign);
+ memcpy(destptr, srcptr, numbytes);
+ return numbytes;
+}
+
+/*
+ * Copy nitems null-bitmap bits from source to destination
+ *
+ * destbitmap: start of destination array's null bitmap (mustn't be NULL)
+ * destoffset: 0-based linear element number of first dest element
+ * srcbitmap: start of source array's null bitmap, or NULL if none
+ * srcoffset: 0-based linear element number of first source element
+ * nitems: number of bits to copy (>= 0)
+ *
+ * If srcbitmap is NULL then we assume the source is all-non-NULL and
+ * fill 1's into the destination bitmap. Note that only the specified
+ * bits in the destination map are changed, not any before or after.
+ *
+ * Note: this could certainly be optimized using standard bitblt methods.
+ * However, it's not clear that the typical Postgres array has enough elements
+ * to make it worth worrying too much. For the moment, KISS.
+ */
+void
+array_bitmap_copy(bits8 *destbitmap, int destoffset,
+ const bits8 *srcbitmap, int srcoffset,
+ int nitems)
+{
+ int destbitmask,
+ destbitval,
+ srcbitmask,
+ srcbitval;
+
+ Assert(destbitmap);
+ if (nitems <= 0)
+ return; /* don't risk fetch off end of memory */
+ destbitmap += destoffset / 8;
+ destbitmask = 1 << (destoffset % 8);
+ destbitval = *destbitmap;
+ if (srcbitmap)
+ {
+ srcbitmap += srcoffset / 8;
+ srcbitmask = 1 << (srcoffset % 8);
+ srcbitval = *srcbitmap;
+ while (nitems-- > 0)
+ {
+ if (srcbitval & srcbitmask)
+ destbitval |= destbitmask;
+ else
+ destbitval &= ~destbitmask;
+ destbitmask <<= 1;
+ if (destbitmask == 0x100)
+ {
+ *destbitmap++ = destbitval;
+ destbitmask = 1;
+ if (nitems > 0)
+ destbitval = *destbitmap;
+ }
+ srcbitmask <<= 1;
+ if (srcbitmask == 0x100)
+ {
+ srcbitmap++;
+ srcbitmask = 1;
+ if (nitems > 0)
+ srcbitval = *srcbitmap;
+ }
+ }
+ if (destbitmask != 1)
+ *destbitmap = destbitval;
+ }
+ else
+ {
+ while (nitems-- > 0)
+ {
+ destbitval |= destbitmask;
+ destbitmask <<= 1;
+ if (destbitmask == 0x100)
+ {
+ *destbitmap++ = destbitval;
+ destbitmask = 1;
+ if (nitems > 0)
+ destbitval = *destbitmap;
+ }
+ }
+ if (destbitmask != 1)
+ *destbitmap = destbitval;
+ }
+}
+
+/*
+ * Compute space needed for a slice of an array
+ *
+ * We assume the caller has verified that the slice coordinates are valid.
+ */
+static int
+array_slice_size(char *arraydataptr, bits8 *arraynullsptr,
+ int ndim, int *dim, int *lb,
+ int *st, int *endp,
+ int typlen, bool typbyval, char typalign)
+{
+ int src_offset,
+ span[MAXDIM],
+ prod[MAXDIM],
+ dist[MAXDIM],
+ indx[MAXDIM];
+ char *ptr;
+ int i,
+ j,
+ inc;
+ int count = 0;
+
+ mda_get_range(ndim, span, st, endp);
+
+ /* Pretty easy for fixed element length without nulls ... */
+ if (typlen > 0 && !arraynullsptr)
+ return ArrayGetNItems(ndim, span) * att_align_nominal(typlen, typalign);
+
+ /* Else gotta do it the hard way */
+ src_offset = ArrayGetOffset(ndim, dim, lb, st);
+ ptr = array_seek(arraydataptr, 0, arraynullsptr, src_offset,
+ typlen, typbyval, typalign);
+ mda_get_prod(ndim, dim, prod);
+ mda_get_offset_values(ndim, dist, prod, span);
+ for (i = 0; i < ndim; i++)
+ indx[i] = 0;
+ j = ndim - 1;
+ do
+ {
+ if (dist[j])
+ {
+ ptr = array_seek(ptr, src_offset, arraynullsptr, dist[j],
+ typlen, typbyval, typalign);
+ src_offset += dist[j];
+ }
+ if (!array_get_isnull(arraynullsptr, src_offset))
+ {
+ inc = att_addlength_pointer(0, typlen, ptr);
+ inc = att_align_nominal(inc, typalign);
+ ptr += inc;
+ count += inc;
+ }
+ src_offset++;
+ } while ((j = mda_next_tuple(ndim, indx, span)) != -1);
+ return count;
+}
+
+/*
+ * Extract a slice of an array into consecutive elements in the destination
+ * array.
+ *
+ * We assume the caller has verified that the slice coordinates are valid,
+ * allocated enough storage for the result, and initialized the header
+ * of the new array.
+ */
+static void
+array_extract_slice(ArrayType *newarray,
+ int ndim,
+ int *dim,
+ int *lb,
+ char *arraydataptr,
+ bits8 *arraynullsptr,
+ int *st,
+ int *endp,
+ int typlen,
+ bool typbyval,
+ char typalign)
+{
+ char *destdataptr = ARR_DATA_PTR(newarray);
+ bits8 *destnullsptr = ARR_NULLBITMAP(newarray);
+ char *srcdataptr;
+ int src_offset,
+ dest_offset,
+ prod[MAXDIM],
+ span[MAXDIM],
+ dist[MAXDIM],
+ indx[MAXDIM];
+ int i,
+ j,
+ inc;
+
+ src_offset = ArrayGetOffset(ndim, dim, lb, st);
+ srcdataptr = array_seek(arraydataptr, 0, arraynullsptr, src_offset,
+ typlen, typbyval, typalign);
+ mda_get_prod(ndim, dim, prod);
+ mda_get_range(ndim, span, st, endp);
+ mda_get_offset_values(ndim, dist, prod, span);
+ for (i = 0; i < ndim; i++)
+ indx[i] = 0;
+ dest_offset = 0;
+ j = ndim - 1;
+ do
+ {
+ if (dist[j])
+ {
+ /* skip unwanted elements */
+ srcdataptr = array_seek(srcdataptr, src_offset, arraynullsptr,
+ dist[j],
+ typlen, typbyval, typalign);
+ src_offset += dist[j];
+ }
+ inc = array_copy(destdataptr, 1,
+ srcdataptr, src_offset, arraynullsptr,
+ typlen, typbyval, typalign);
+ if (destnullsptr)
+ array_bitmap_copy(destnullsptr, dest_offset,
+ arraynullsptr, src_offset,
+ 1);
+ destdataptr += inc;
+ srcdataptr += inc;
+ src_offset++;
+ dest_offset++;
+ } while ((j = mda_next_tuple(ndim, indx, span)) != -1);
+}
+
+/*
+ * Insert a slice into an array.
+ *
+ * ndim/dim[]/lb[] are dimensions of the original array. A new array with
+ * those same dimensions is to be constructed. destArray must already
+ * have been allocated and its header initialized.
+ *
+ * st[]/endp[] identify the slice to be replaced. Elements within the slice
+ * volume are taken from consecutive elements of the srcArray; elements
+ * outside it are copied from origArray.
+ *
+ * We assume the caller has verified that the slice coordinates are valid.
+ */
+static void
+array_insert_slice(ArrayType *destArray,
+ ArrayType *origArray,
+ ArrayType *srcArray,
+ int ndim,
+ int *dim,
+ int *lb,
+ int *st,
+ int *endp,
+ int typlen,
+ bool typbyval,
+ char typalign)
+{
+ char *destPtr = ARR_DATA_PTR(destArray);
+ char *origPtr = ARR_DATA_PTR(origArray);
+ char *srcPtr = ARR_DATA_PTR(srcArray);
+ bits8 *destBitmap = ARR_NULLBITMAP(destArray);
+ bits8 *origBitmap = ARR_NULLBITMAP(origArray);
+ bits8 *srcBitmap = ARR_NULLBITMAP(srcArray);
+ int orignitems = ArrayGetNItems(ARR_NDIM(origArray),
+ ARR_DIMS(origArray));
+ int dest_offset,
+ orig_offset,
+ src_offset,
+ prod[MAXDIM],
+ span[MAXDIM],
+ dist[MAXDIM],
+ indx[MAXDIM];
+ int i,
+ j,
+ inc;
+
+ dest_offset = ArrayGetOffset(ndim, dim, lb, st);
+ /* copy items before the slice start */
+ inc = array_copy(destPtr, dest_offset,
+ origPtr, 0, origBitmap,
+ typlen, typbyval, typalign);
+ destPtr += inc;
+ origPtr += inc;
+ if (destBitmap)
+ array_bitmap_copy(destBitmap, 0, origBitmap, 0, dest_offset);
+ orig_offset = dest_offset;
+ mda_get_prod(ndim, dim, prod);
+ mda_get_range(ndim, span, st, endp);
+ mda_get_offset_values(ndim, dist, prod, span);
+ for (i = 0; i < ndim; i++)
+ indx[i] = 0;
+ src_offset = 0;
+ j = ndim - 1;
+ do
+ {
+ /* Copy/advance over elements between here and next part of slice */
+ if (dist[j])
+ {
+ inc = array_copy(destPtr, dist[j],
+ origPtr, orig_offset, origBitmap,
+ typlen, typbyval, typalign);
+ destPtr += inc;
+ origPtr += inc;
+ if (destBitmap)
+ array_bitmap_copy(destBitmap, dest_offset,
+ origBitmap, orig_offset,
+ dist[j]);
+ dest_offset += dist[j];
+ orig_offset += dist[j];
+ }
+ /* Copy new element at this slice position */
+ inc = array_copy(destPtr, 1,
+ srcPtr, src_offset, srcBitmap,
+ typlen, typbyval, typalign);
+ if (destBitmap)
+ array_bitmap_copy(destBitmap, dest_offset,
+ srcBitmap, src_offset,
+ 1);
+ destPtr += inc;
+ srcPtr += inc;
+ dest_offset++;
+ src_offset++;
+ /* Advance over old element at this slice position */
+ origPtr = array_seek(origPtr, orig_offset, origBitmap, 1,
+ typlen, typbyval, typalign);
+ orig_offset++;
+ } while ((j = mda_next_tuple(ndim, indx, span)) != -1);
+
+ /* don't miss any data at the end */
+ array_copy(destPtr, orignitems - orig_offset,
+ origPtr, orig_offset, origBitmap,
+ typlen, typbyval, typalign);
+ if (destBitmap)
+ array_bitmap_copy(destBitmap, dest_offset,
+ origBitmap, orig_offset,
+ orignitems - orig_offset);
+}
+
+/*
+ * initArrayResult - initialize an empty ArrayBuildState
+ *
+ * element_type is the array element type (must be a valid array element type)
+ * rcontext is where to keep working state
+ * subcontext is a flag determining whether to use a separate memory context
+ *
+ * Note: there are two common schemes for using accumArrayResult().
+ * In the older scheme, you start with a NULL ArrayBuildState pointer, and
+ * call accumArrayResult once per element. In this scheme you end up with
+ * a NULL pointer if there were no elements, which you need to special-case.
+ * In the newer scheme, call initArrayResult and then call accumArrayResult
+ * once per element. In this scheme you always end with a non-NULL pointer
+ * that you can pass to makeArrayResult; you get an empty array if there
+ * were no elements. This is preferred if an empty array is what you want.
+ *
+ * It's possible to choose whether to create a separate memory context for the
+ * array build state, or whether to allocate it directly within rcontext.
+ *
+ * When there are many concurrent small states (e.g. array_agg() using hash
+ * aggregation of many small groups), using a separate memory context for each
+ * one may result in severe memory bloat. In such cases, use the same memory
+ * context to initialize all such array build states, and pass
+ * subcontext=false.
+ *
+ * In cases when the array build states have different lifetimes, using a
+ * single memory context is impractical. Instead, pass subcontext=true so that
+ * the array build states can be freed individually.
+ */
+ArrayBuildState *
+initArrayResult(Oid element_type, MemoryContext rcontext, bool subcontext)
+{
+ ArrayBuildState *astate;
+ MemoryContext arr_context = rcontext;
+
+ /* Make a temporary context to hold all the junk */
+ if (subcontext)
+ arr_context = AllocSetContextCreate(rcontext,
+ "accumArrayResult",
+ ALLOCSET_DEFAULT_SIZES);
+
+ astate = (ArrayBuildState *)
+ MemoryContextAlloc(arr_context, sizeof(ArrayBuildState));
+ astate->mcontext = arr_context;
+ astate->private_cxt = subcontext;
+ astate->alen = (subcontext ? 64 : 8); /* arbitrary starting array size */
+ astate->dvalues = (Datum *)
+ MemoryContextAlloc(arr_context, astate->alen * sizeof(Datum));
+ astate->dnulls = (bool *)
+ MemoryContextAlloc(arr_context, astate->alen * sizeof(bool));
+ astate->nelems = 0;
+ astate->element_type = element_type;
+ get_typlenbyvalalign(element_type,
+ &astate->typlen,
+ &astate->typbyval,
+ &astate->typalign);
+
+ return astate;
+}
+
+/*
+ * accumArrayResult - accumulate one (more) Datum for an array result
+ *
+ * astate is working state (can be NULL on first call)
+ * dvalue/disnull represent the new Datum to append to the array
+ * element_type is the Datum's type (must be a valid array element type)
+ * rcontext is where to keep working state
+ */
+ArrayBuildState *
+accumArrayResult(ArrayBuildState *astate,
+ Datum dvalue, bool disnull,
+ Oid element_type,
+ MemoryContext rcontext)
+{
+ MemoryContext oldcontext;
+
+ if (astate == NULL)
+ {
+ /* First time through --- initialize */
+ astate = initArrayResult(element_type, rcontext, true);
+ }
+ else
+ {
+ Assert(astate->element_type == element_type);
+ }
+
+ oldcontext = MemoryContextSwitchTo(astate->mcontext);
+
+ /* enlarge dvalues[]/dnulls[] if needed */
+ if (astate->nelems >= astate->alen)
+ {
+ astate->alen *= 2;
+ astate->dvalues = (Datum *)
+ repalloc(astate->dvalues, astate->alen * sizeof(Datum));
+ astate->dnulls = (bool *)
+ repalloc(astate->dnulls, astate->alen * sizeof(bool));
+ }
+
+ /*
+ * Ensure pass-by-ref stuff is copied into mcontext; and detoast it too if
+ * it's varlena. (You might think that detoasting is not needed here
+ * because construct_md_array can detoast the array elements later.
+ * However, we must not let construct_md_array modify the ArrayBuildState
+ * because that would mean array_agg_finalfn damages its input, which is
+ * verboten. Also, this way frequently saves one copying step.)
+ */
+ if (!disnull && !astate->typbyval)
+ {
+ if (astate->typlen == -1)
+ dvalue = PointerGetDatum(PG_DETOAST_DATUM_COPY(dvalue));
+ else
+ dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
+ }
+
+ astate->dvalues[astate->nelems] = dvalue;
+ astate->dnulls[astate->nelems] = disnull;
+ astate->nelems++;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return astate;
+}
+
+/*
+ * makeArrayResult - produce 1-D final result of accumArrayResult
+ *
+ * Note: only releases astate if it was initialized within a separate memory
+ * context (i.e. using subcontext=true when calling initArrayResult).
+ *
+ * astate is working state (must not be NULL)
+ * rcontext is where to construct result
+ */
+Datum
+makeArrayResult(ArrayBuildState *astate,
+ MemoryContext rcontext)
+{
+ int ndims;
+ int dims[1];
+ int lbs[1];
+
+ /* If no elements were presented, we want to create an empty array */
+ ndims = (astate->nelems > 0) ? 1 : 0;
+ dims[0] = astate->nelems;
+ lbs[0] = 1;
+
+ return makeMdArrayResult(astate, ndims, dims, lbs, rcontext,
+ astate->private_cxt);
+}
+
+/*
+ * makeMdArrayResult - produce multi-D final result of accumArrayResult
+ *
+ * beware: no check that specified dimensions match the number of values
+ * accumulated.
+ *
+ * Note: if the astate was not initialized within a separate memory context
+ * (that is, initArrayResult was called with subcontext=false), then using
+ * release=true is illegal. Instead, release astate along with the rest of its
+ * context when appropriate.
+ *
+ * astate is working state (must not be NULL)
+ * rcontext is where to construct result
+ * release is true if okay to release working state
+ */
+Datum
+makeMdArrayResult(ArrayBuildState *astate,
+ int ndims,
+ int *dims,
+ int *lbs,
+ MemoryContext rcontext,
+ bool release)
+{
+ ArrayType *result;
+ MemoryContext oldcontext;
+
+ /* Build the final array result in rcontext */
+ oldcontext = MemoryContextSwitchTo(rcontext);
+
+ result = construct_md_array(astate->dvalues,
+ astate->dnulls,
+ ndims,
+ dims,
+ lbs,
+ astate->element_type,
+ astate->typlen,
+ astate->typbyval,
+ astate->typalign);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Clean up all the junk */
+ if (release)
+ {
+ Assert(astate->private_cxt);
+ MemoryContextDelete(astate->mcontext);
+ }
+
+ return PointerGetDatum(result);
+}
+
+/*
+ * The following three functions provide essentially the same API as
+ * initArrayResult/accumArrayResult/makeArrayResult, but instead of accepting
+ * inputs that are array elements, they accept inputs that are arrays and
+ * produce an output array having N+1 dimensions. The inputs must all have
+ * identical dimensionality as well as element type.
+ */
+
+/*
+ * initArrayResultArr - initialize an empty ArrayBuildStateArr
+ *
+ * array_type is the array type (must be a valid varlena array type)
+ * element_type is the type of the array's elements (lookup if InvalidOid)
+ * rcontext is where to keep working state
+ * subcontext is a flag determining whether to use a separate memory context
+ */
+ArrayBuildStateArr *
+initArrayResultArr(Oid array_type, Oid element_type, MemoryContext rcontext,
+ bool subcontext)
+{
+ ArrayBuildStateArr *astate;
+ MemoryContext arr_context = rcontext; /* by default use the parent ctx */
+
+ /* Lookup element type, unless element_type already provided */
+ if (!OidIsValid(element_type))
+ {
+ element_type = get_element_type(array_type);
+
+ if (!OidIsValid(element_type))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("data type %s is not an array type",
+ format_type_be(array_type))));
+ }
+
+ /* Make a temporary context to hold all the junk */
+ if (subcontext)
+ arr_context = AllocSetContextCreate(rcontext,
+ "accumArrayResultArr",
+ ALLOCSET_DEFAULT_SIZES);
+
+ /* Note we initialize all fields to zero */
+ astate = (ArrayBuildStateArr *)
+ MemoryContextAllocZero(arr_context, sizeof(ArrayBuildStateArr));
+ astate->mcontext = arr_context;
+ astate->private_cxt = subcontext;
+
+ /* Save relevant datatype information */
+ astate->array_type = array_type;
+ astate->element_type = element_type;
+
+ return astate;
+}
+
+/*
+ * accumArrayResultArr - accumulate one (more) sub-array for an array result
+ *
+ * astate is working state (can be NULL on first call)
+ * dvalue/disnull represent the new sub-array to append to the array
+ * array_type is the array type (must be a valid varlena array type)
+ * rcontext is where to keep working state
+ */
+ArrayBuildStateArr *
+accumArrayResultArr(ArrayBuildStateArr *astate,
+ Datum dvalue, bool disnull,
+ Oid array_type,
+ MemoryContext rcontext)
+{
+ ArrayType *arg;
+ MemoryContext oldcontext;
+ int *dims,
+ *lbs,
+ ndims,
+ nitems,
+ ndatabytes;
+ char *data;
+ int i;
+
+ /*
+ * We disallow accumulating null subarrays. Another plausible definition
+ * is to ignore them, but callers that want that can just skip calling
+ * this function.
+ */
+ if (disnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("cannot accumulate null arrays")));
+
+ /* Detoast input array in caller's context */
+ arg = DatumGetArrayTypeP(dvalue);
+
+ if (astate == NULL)
+ astate = initArrayResultArr(array_type, InvalidOid, rcontext, true);
+ else
+ Assert(astate->array_type == array_type);
+
+ oldcontext = MemoryContextSwitchTo(astate->mcontext);
+
+ /* Collect this input's dimensions */
+ ndims = ARR_NDIM(arg);
+ dims = ARR_DIMS(arg);
+ lbs = ARR_LBOUND(arg);
+ data = ARR_DATA_PTR(arg);
+ nitems = ArrayGetNItems(ndims, dims);
+ ndatabytes = ARR_SIZE(arg) - ARR_DATA_OFFSET(arg);
+
+ if (astate->ndims == 0)
+ {
+ /* First input; check/save the dimensionality info */
+
+ /* Should we allow empty inputs and just produce an empty output? */
+ if (ndims == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("cannot accumulate empty arrays")));
+ if (ndims + 1 > MAXDIM)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
+ ndims + 1, MAXDIM)));
+
+ /*
+ * The output array will have n+1 dimensions, with the ones after the
+ * first matching the input's dimensions.
+ */
+ astate->ndims = ndims + 1;
+ astate->dims[0] = 0;
+ memcpy(&astate->dims[1], dims, ndims * sizeof(int));
+ astate->lbs[0] = 1;
+ memcpy(&astate->lbs[1], lbs, ndims * sizeof(int));
+
+ /* Allocate at least enough data space for this item */
+ astate->abytes = pg_nextpower2_32(Max(1024, ndatabytes + 1));
+ astate->data = (char *) palloc(astate->abytes);
+ }
+ else
+ {
+ /* Second or later input: must match first input's dimensionality */
+ if (astate->ndims != ndims + 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("cannot accumulate arrays of different dimensionality")));
+ for (i = 0; i < ndims; i++)
+ {
+ if (astate->dims[i + 1] != dims[i] || astate->lbs[i + 1] != lbs[i])
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("cannot accumulate arrays of different dimensionality")));
+ }
+
+ /* Enlarge data space if needed */
+ if (astate->nbytes + ndatabytes > astate->abytes)
+ {
+ astate->abytes = Max(astate->abytes * 2,
+ astate->nbytes + ndatabytes);
+ astate->data = (char *) repalloc(astate->data, astate->abytes);
+ }
+ }
+
+ /*
+ * Copy the data portion of the sub-array. Note we assume that the
+ * advertised data length of the sub-array is properly aligned. We do not
+ * have to worry about detoasting elements since whatever's in the
+ * sub-array should be OK already.
+ */
+ memcpy(astate->data + astate->nbytes, data, ndatabytes);
+ astate->nbytes += ndatabytes;
+
+ /* Deal with null bitmap if needed */
+ if (astate->nullbitmap || ARR_HASNULL(arg))
+ {
+ int newnitems = astate->nitems + nitems;
+
+ if (astate->nullbitmap == NULL)
+ {
+ /*
+ * First input with nulls; we must retrospectively handle any
+ * previous inputs by marking all their items non-null.
+ */
+ astate->aitems = pg_nextpower2_32(Max(256, newnitems + 1));
+ astate->nullbitmap = (bits8 *) palloc((astate->aitems + 7) / 8);
+ array_bitmap_copy(astate->nullbitmap, 0,
+ NULL, 0,
+ astate->nitems);
+ }
+ else if (newnitems > astate->aitems)
+ {
+ astate->aitems = Max(astate->aitems * 2, newnitems);
+ astate->nullbitmap = (bits8 *)
+ repalloc(astate->nullbitmap, (astate->aitems + 7) / 8);
+ }
+ array_bitmap_copy(astate->nullbitmap, astate->nitems,
+ ARR_NULLBITMAP(arg), 0,
+ nitems);
+ }
+
+ astate->nitems += nitems;
+ astate->dims[0] += 1;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Release detoasted copy if any */
+ if ((Pointer) arg != DatumGetPointer(dvalue))
+ pfree(arg);
+
+ return astate;
+}
+
+/*
+ * makeArrayResultArr - produce N+1-D final result of accumArrayResultArr
+ *
+ * astate is working state (must not be NULL)
+ * rcontext is where to construct result
+ * release is true if okay to release working state
+ */
+Datum
+makeArrayResultArr(ArrayBuildStateArr *astate,
+ MemoryContext rcontext,
+ bool release)
+{
+ ArrayType *result;
+ MemoryContext oldcontext;
+
+ /* Build the final array result in rcontext */
+ oldcontext = MemoryContextSwitchTo(rcontext);
+
+ if (astate->ndims == 0)
+ {
+ /* No inputs, return empty array */
+ result = construct_empty_array(astate->element_type);
+ }
+ else
+ {
+ int dataoffset,
+ nbytes;
+
+ /* Check for overflow of the array dimensions */
+ (void) ArrayGetNItems(astate->ndims, astate->dims);
+ ArrayCheckBounds(astate->ndims, astate->dims, astate->lbs);
+
+ /* Compute required space */
+ nbytes = astate->nbytes;
+ if (astate->nullbitmap != NULL)
+ {
+ dataoffset = ARR_OVERHEAD_WITHNULLS(astate->ndims, astate->nitems);
+ nbytes += dataoffset;
+ }
+ else
+ {
+ dataoffset = 0;
+ nbytes += ARR_OVERHEAD_NONULLS(astate->ndims);
+ }
+
+ result = (ArrayType *) palloc0(nbytes);
+ SET_VARSIZE(result, nbytes);
+ result->ndim = astate->ndims;
+ result->dataoffset = dataoffset;
+ result->elemtype = astate->element_type;
+
+ memcpy(ARR_DIMS(result), astate->dims, astate->ndims * sizeof(int));
+ memcpy(ARR_LBOUND(result), astate->lbs, astate->ndims * sizeof(int));
+ memcpy(ARR_DATA_PTR(result), astate->data, astate->nbytes);
+
+ if (astate->nullbitmap != NULL)
+ array_bitmap_copy(ARR_NULLBITMAP(result), 0,
+ astate->nullbitmap, 0,
+ astate->nitems);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Clean up all the junk */
+ if (release)
+ {
+ Assert(astate->private_cxt);
+ MemoryContextDelete(astate->mcontext);
+ }
+
+ return PointerGetDatum(result);
+}
+
+/*
+ * The following three functions provide essentially the same API as
+ * initArrayResult/accumArrayResult/makeArrayResult, but can accept either
+ * scalar or array inputs, invoking the appropriate set of functions above.
+ */
+
+/*
+ * initArrayResultAny - initialize an empty ArrayBuildStateAny
+ *
+ * input_type is the input datatype (either element or array type)
+ * rcontext is where to keep working state
+ * subcontext is a flag determining whether to use a separate memory context
+ */
+ArrayBuildStateAny *
+initArrayResultAny(Oid input_type, MemoryContext rcontext, bool subcontext)
+{
+ ArrayBuildStateAny *astate;
+ Oid element_type = get_element_type(input_type);
+
+ if (OidIsValid(element_type))
+ {
+ /* Array case */
+ ArrayBuildStateArr *arraystate;
+
+ arraystate = initArrayResultArr(input_type, InvalidOid, rcontext, subcontext);
+ astate = (ArrayBuildStateAny *)
+ MemoryContextAlloc(arraystate->mcontext,
+ sizeof(ArrayBuildStateAny));
+ astate->scalarstate = NULL;
+ astate->arraystate = arraystate;
+ }
+ else
+ {
+ /* Scalar case */
+ ArrayBuildState *scalarstate;
+
+ /* Let's just check that we have a type that can be put into arrays */
+ Assert(OidIsValid(get_array_type(input_type)));
+
+ scalarstate = initArrayResult(input_type, rcontext, subcontext);
+ astate = (ArrayBuildStateAny *)
+ MemoryContextAlloc(scalarstate->mcontext,
+ sizeof(ArrayBuildStateAny));
+ astate->scalarstate = scalarstate;
+ astate->arraystate = NULL;
+ }
+
+ return astate;
+}
+
+/*
+ * accumArrayResultAny - accumulate one (more) input for an array result
+ *
+ * astate is working state (can be NULL on first call)
+ * dvalue/disnull represent the new input to append to the array
+ * input_type is the input datatype (either element or array type)
+ * rcontext is where to keep working state
+ */
+ArrayBuildStateAny *
+accumArrayResultAny(ArrayBuildStateAny *astate,
+ Datum dvalue, bool disnull,
+ Oid input_type,
+ MemoryContext rcontext)
+{
+ if (astate == NULL)
+ astate = initArrayResultAny(input_type, rcontext, true);
+
+ if (astate->scalarstate)
+ (void) accumArrayResult(astate->scalarstate,
+ dvalue, disnull,
+ input_type, rcontext);
+ else
+ (void) accumArrayResultArr(astate->arraystate,
+ dvalue, disnull,
+ input_type, rcontext);
+
+ return astate;
+}
+
+/*
+ * makeArrayResultAny - produce final result of accumArrayResultAny
+ *
+ * astate is working state (must not be NULL)
+ * rcontext is where to construct result
+ * release is true if okay to release working state
+ */
+Datum
+makeArrayResultAny(ArrayBuildStateAny *astate,
+ MemoryContext rcontext, bool release)
+{
+ Datum result;
+
+ if (astate->scalarstate)
+ {
+ /* Must use makeMdArrayResult to support "release" parameter */
+ int ndims;
+ int dims[1];
+ int lbs[1];
+
+ /* If no elements were presented, we want to create an empty array */
+ ndims = (astate->scalarstate->nelems > 0) ? 1 : 0;
+ dims[0] = astate->scalarstate->nelems;
+ lbs[0] = 1;
+
+ result = makeMdArrayResult(astate->scalarstate, ndims, dims, lbs,
+ rcontext, release);
+ }
+ else
+ {
+ result = makeArrayResultArr(astate->arraystate,
+ rcontext, release);
+ }
+ return result;
+}
+
+
+Datum
+array_larger(PG_FUNCTION_ARGS)
+{
+ if (array_cmp(fcinfo) > 0)
+ PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+ else
+ PG_RETURN_DATUM(PG_GETARG_DATUM(1));
+}
+
+Datum
+array_smaller(PG_FUNCTION_ARGS)
+{
+ if (array_cmp(fcinfo) < 0)
+ PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+ else
+ PG_RETURN_DATUM(PG_GETARG_DATUM(1));
+}
+
+
+typedef struct generate_subscripts_fctx
+{
+ int32 lower;
+ int32 upper;
+ bool reverse;
+} generate_subscripts_fctx;
+
+/*
+ * generate_subscripts(array anyarray, dim int [, reverse bool])
+ * Returns all subscripts of the array for any dimension
+ */
+Datum
+generate_subscripts(PG_FUNCTION_ARGS)
+{
+ FuncCallContext *funcctx;
+ MemoryContext oldcontext;
+ generate_subscripts_fctx *fctx;
+
+ /* stuff done only on the first call of the function */
+ if (SRF_IS_FIRSTCALL())
+ {
+ AnyArrayType *v = PG_GETARG_ANY_ARRAY_P(0);
+ int reqdim = PG_GETARG_INT32(1);
+ int *lb,
+ *dimv;
+
+ /* create a function context for cross-call persistence */
+ funcctx = SRF_FIRSTCALL_INIT();
+
+ /* Sanity check: does it look like an array at all? */
+ if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM)
+ SRF_RETURN_DONE(funcctx);
+
+ /* Sanity check: was the requested dim valid */
+ if (reqdim <= 0 || reqdim > AARR_NDIM(v))
+ SRF_RETURN_DONE(funcctx);
+
+ /*
+ * switch to memory context appropriate for multiple function calls
+ */
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+ fctx = (generate_subscripts_fctx *) palloc(sizeof(generate_subscripts_fctx));
+
+ lb = AARR_LBOUND(v);
+ dimv = AARR_DIMS(v);
+
+ fctx->lower = lb[reqdim - 1];
+ fctx->upper = dimv[reqdim - 1] + lb[reqdim - 1] - 1;
+ fctx->reverse = (PG_NARGS() < 3) ? false : PG_GETARG_BOOL(2);
+
+ funcctx->user_fctx = fctx;
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ funcctx = SRF_PERCALL_SETUP();
+
+ fctx = funcctx->user_fctx;
+
+ if (fctx->lower <= fctx->upper)
+ {
+ if (!fctx->reverse)
+ SRF_RETURN_NEXT(funcctx, Int32GetDatum(fctx->lower++));
+ else
+ SRF_RETURN_NEXT(funcctx, Int32GetDatum(fctx->upper--));
+ }
+ else
+ /* done when there are no more elements left */
+ SRF_RETURN_DONE(funcctx);
+}
+
+/*
+ * generate_subscripts_nodir
+ * Implements the 2-argument version of generate_subscripts
+ */
+Datum
+generate_subscripts_nodir(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return generate_subscripts(fcinfo);
+}
+
+/*
+ * array_fill_with_lower_bounds
+ * Create and fill array with defined lower bounds.
+ */
+Datum
+array_fill_with_lower_bounds(PG_FUNCTION_ARGS)
+{
+ ArrayType *dims;
+ ArrayType *lbs;
+ ArrayType *result;
+ Oid elmtype;
+ Datum value;
+ bool isnull;
+
+ if (PG_ARGISNULL(1) || PG_ARGISNULL(2))
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("dimension array or low bound array cannot be null")));
+
+ dims = PG_GETARG_ARRAYTYPE_P(1);
+ lbs = PG_GETARG_ARRAYTYPE_P(2);
+
+ if (!PG_ARGISNULL(0))
+ {
+ value = PG_GETARG_DATUM(0);
+ isnull = false;
+ }
+ else
+ {
+ value = 0;
+ isnull = true;
+ }
+
+ elmtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ if (!OidIsValid(elmtype))
+ elog(ERROR, "could not determine data type of input");
+
+ result = array_fill_internal(dims, lbs, value, isnull, elmtype, fcinfo);
+ PG_RETURN_ARRAYTYPE_P(result);
+}
+
+/*
+ * array_fill
+ * Create and fill array with default lower bounds.
+ */
+Datum
+array_fill(PG_FUNCTION_ARGS)
+{
+ ArrayType *dims;
+ ArrayType *result;
+ Oid elmtype;
+ Datum value;
+ bool isnull;
+
+ if (PG_ARGISNULL(1))
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("dimension array or low bound array cannot be null")));
+
+ dims = PG_GETARG_ARRAYTYPE_P(1);
+
+ if (!PG_ARGISNULL(0))
+ {
+ value = PG_GETARG_DATUM(0);
+ isnull = false;
+ }
+ else
+ {
+ value = 0;
+ isnull = true;
+ }
+
+ elmtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ if (!OidIsValid(elmtype))
+ elog(ERROR, "could not determine data type of input");
+
+ result = array_fill_internal(dims, NULL, value, isnull, elmtype, fcinfo);
+ PG_RETURN_ARRAYTYPE_P(result);
+}
+
+static ArrayType *
+create_array_envelope(int ndims, int *dimv, int *lbsv, int nbytes,
+ Oid elmtype, int dataoffset)
+{
+ ArrayType *result;
+
+ result = (ArrayType *) palloc0(nbytes);
+ SET_VARSIZE(result, nbytes);
+ result->ndim = ndims;
+ result->dataoffset = dataoffset;
+ result->elemtype = elmtype;
+ memcpy(ARR_DIMS(result), dimv, ndims * sizeof(int));
+ memcpy(ARR_LBOUND(result), lbsv, ndims * sizeof(int));
+
+ return result;
+}
+
+static ArrayType *
+array_fill_internal(ArrayType *dims, ArrayType *lbs,
+ Datum value, bool isnull, Oid elmtype,
+ FunctionCallInfo fcinfo)
+{
+ ArrayType *result;
+ int *dimv;
+ int *lbsv;
+ int ndims;
+ int nitems;
+ int deflbs[MAXDIM];
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
+ ArrayMetaState *my_extra;
+
+ /*
+ * Params checks
+ */
+ if (ARR_NDIM(dims) > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("wrong number of array subscripts"),
+ errdetail("Dimension array must be one dimensional.")));
+
+ if (array_contains_nulls(dims))
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("dimension values cannot be null")));
+
+ dimv = (int *) ARR_DATA_PTR(dims);
+ ndims = (ARR_NDIM(dims) > 0) ? ARR_DIMS(dims)[0] : 0;
+
+ if (ndims < 0) /* we do allow zero-dimension arrays */
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid number of dimensions: %d", ndims)));
+ if (ndims > MAXDIM)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
+ ndims, MAXDIM)));
+
+ if (lbs != NULL)
+ {
+ if (ARR_NDIM(lbs) > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("wrong number of array subscripts"),
+ errdetail("Dimension array must be one dimensional.")));
+
+ if (array_contains_nulls(lbs))
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("dimension values cannot be null")));
+
+ if (ndims != ((ARR_NDIM(lbs) > 0) ? ARR_DIMS(lbs)[0] : 0))
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("wrong number of array subscripts"),
+ errdetail("Low bound array has different size than dimensions array.")));
+
+ lbsv = (int *) ARR_DATA_PTR(lbs);
+ }
+ else
+ {
+ int i;
+
+ for (i = 0; i < MAXDIM; i++)
+ deflbs[i] = 1;
+
+ lbsv = deflbs;
+ }
+
+ /* This checks for overflow of the array dimensions */
+ nitems = ArrayGetNItems(ndims, dimv);
+ ArrayCheckBounds(ndims, dimv, lbsv);
+
+ /* fast track for empty array */
+ if (nitems <= 0)
+ return construct_empty_array(elmtype);
+
+ /*
+ * We arrange to look up info about element type 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 = InvalidOid;
+ }
+
+ if (my_extra->element_type != elmtype)
+ {
+ /* Get info about element type */
+ get_typlenbyvalalign(elmtype,
+ &my_extra->typlen,
+ &my_extra->typbyval,
+ &my_extra->typalign);
+ my_extra->element_type = elmtype;
+ }
+
+ elmlen = my_extra->typlen;
+ elmbyval = my_extra->typbyval;
+ elmalign = my_extra->typalign;
+
+ /* compute required space */
+ if (!isnull)
+ {
+ int i;
+ char *p;
+ int nbytes;
+ int totbytes;
+
+ /* make sure data is not toasted */
+ if (elmlen == -1)
+ value = PointerGetDatum(PG_DETOAST_DATUM(value));
+
+ nbytes = att_addlength_datum(0, elmlen, value);
+ nbytes = att_align_nominal(nbytes, elmalign);
+ Assert(nbytes > 0);
+
+ totbytes = nbytes * nitems;
+
+ /* check for overflow of multiplication or total request */
+ if (totbytes / nbytes != nitems ||
+ !AllocSizeIsValid(totbytes))
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("array size exceeds the maximum allowed (%d)",
+ (int) MaxAllocSize)));
+
+ /*
+ * This addition can't overflow, but it might cause us to go past
+ * MaxAllocSize. We leave it to palloc to complain in that case.
+ */
+ totbytes += ARR_OVERHEAD_NONULLS(ndims);
+
+ result = create_array_envelope(ndims, dimv, lbsv, totbytes,
+ elmtype, 0);
+
+ p = ARR_DATA_PTR(result);
+ for (i = 0; i < nitems; i++)
+ p += ArrayCastAndSet(value, elmlen, elmbyval, elmalign, p);
+ }
+ else
+ {
+ int nbytes;
+ int dataoffset;
+
+ dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems);
+ nbytes = dataoffset;
+
+ result = create_array_envelope(ndims, dimv, lbsv, nbytes,
+ elmtype, dataoffset);
+
+ /* create_array_envelope already zeroed the bitmap, so we're done */
+ }
+
+ return result;
+}
+
+
+/*
+ * UNNEST
+ */
+Datum
+array_unnest(PG_FUNCTION_ARGS)
+{
+ typedef struct
+ {
+ array_iter iter;
+ int nextelem;
+ int numelems;
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
+ } array_unnest_fctx;
+
+ FuncCallContext *funcctx;
+ array_unnest_fctx *fctx;
+ MemoryContext oldcontext;
+
+ /* stuff done only on the first call of the function */
+ if (SRF_IS_FIRSTCALL())
+ {
+ AnyArrayType *arr;
+
+ /* create a function context for cross-call persistence */
+ funcctx = SRF_FIRSTCALL_INIT();
+
+ /*
+ * switch to memory context appropriate for multiple function calls
+ */
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ /*
+ * Get the array value and detoast if needed. We can't do this
+ * earlier because if we have to detoast, we want the detoasted copy
+ * to be in multi_call_memory_ctx, so it will go away when we're done
+ * and not before. (If no detoast happens, we assume the originally
+ * passed array will stick around till then.)
+ */
+ arr = PG_GETARG_ANY_ARRAY_P(0);
+
+ /* allocate memory for user context */
+ fctx = (array_unnest_fctx *) palloc(sizeof(array_unnest_fctx));
+
+ /* initialize state */
+ array_iter_setup(&fctx->iter, arr);
+ fctx->nextelem = 0;
+ fctx->numelems = ArrayGetNItems(AARR_NDIM(arr), AARR_DIMS(arr));
+
+ if (VARATT_IS_EXPANDED_HEADER(arr))
+ {
+ /* we can just grab the type data from expanded array */
+ fctx->elmlen = arr->xpn.typlen;
+ fctx->elmbyval = arr->xpn.typbyval;
+ fctx->elmalign = arr->xpn.typalign;
+ }
+ else
+ get_typlenbyvalalign(AARR_ELEMTYPE(arr),
+ &fctx->elmlen,
+ &fctx->elmbyval,
+ &fctx->elmalign);
+
+ funcctx->user_fctx = fctx;
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ /* stuff done on every call of the function */
+ funcctx = SRF_PERCALL_SETUP();
+ fctx = funcctx->user_fctx;
+
+ if (fctx->nextelem < fctx->numelems)
+ {
+ int offset = fctx->nextelem++;
+ Datum elem;
+
+ elem = array_iter_next(&fctx->iter, &fcinfo->isnull, offset,
+ fctx->elmlen, fctx->elmbyval, fctx->elmalign);
+
+ SRF_RETURN_NEXT(funcctx, elem);
+ }
+ else
+ {
+ /* do when there is no more left */
+ SRF_RETURN_DONE(funcctx);
+ }
+}
+
+/*
+ * Planner support function for array_unnest(anyarray)
+ */
+Datum
+array_unnest_support(PG_FUNCTION_ARGS)
+{
+ Node *rawreq = (Node *) PG_GETARG_POINTER(0);
+ Node *ret = NULL;
+
+ if (IsA(rawreq, SupportRequestRows))
+ {
+ /* Try to estimate the number of rows returned */
+ SupportRequestRows *req = (SupportRequestRows *) rawreq;
+
+ if (is_funcclause(req->node)) /* be paranoid */
+ {
+ List *args = ((FuncExpr *) req->node)->args;
+ Node *arg1;
+
+ /* We can use estimated argument values here */
+ arg1 = estimate_expression_value(req->root, linitial(args));
+
+ req->rows = estimate_array_length(arg1);
+ ret = (Node *) req;
+ }
+ }
+
+ PG_RETURN_POINTER(ret);
+}
+
+
+/*
+ * array_replace/array_remove support
+ *
+ * Find all array entries matching (not distinct from) search/search_isnull,
+ * and delete them if remove is true, else replace them with
+ * replace/replace_isnull. Comparisons are done using the specified
+ * collation. fcinfo is passed only for caching purposes.
+ */
+static ArrayType *
+array_replace_internal(ArrayType *array,
+ Datum search, bool search_isnull,
+ Datum replace, bool replace_isnull,
+ bool remove, Oid collation,
+ FunctionCallInfo fcinfo)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ ArrayType *result;
+ Oid element_type;
+ Datum *values;
+ bool *nulls;
+ int *dim;
+ int ndim;
+ int nitems,
+ nresult;
+ int i;
+ int32 nbytes = 0;
+ int32 dataoffset;
+ bool hasnulls;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ char *arraydataptr;
+ bits8 *bitmap;
+ int bitmask;
+ bool changed = false;
+ TypeCacheEntry *typentry;
+
+ element_type = ARR_ELEMTYPE(array);
+ ndim = ARR_NDIM(array);
+ dim = ARR_DIMS(array);
+ nitems = ArrayGetNItems(ndim, dim);
+
+ /* Return input array unmodified if it is empty */
+ if (nitems <= 0)
+ return array;
+
+ /*
+ * We can't remove elements from multi-dimensional arrays, since the
+ * result might not be rectangular.
+ */
+ if (remove && ndim > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("removing elements from multidimensional arrays is not supported")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us.
+ */
+ typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != element_type)
+ {
+ 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))));
+ fcinfo->flinfo->fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Detoast values if they are toasted. The replacement value must be
+ * detoasted for insertion into the result array, while detoasting the
+ * search value only once saves cycles.
+ */
+ if (typlen == -1)
+ {
+ if (!search_isnull)
+ search = PointerGetDatum(PG_DETOAST_DATUM(search));
+ if (!replace_isnull)
+ replace = PointerGetDatum(PG_DETOAST_DATUM(replace));
+ }
+
+ /* Prepare to apply the comparison operator */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Allocate temporary arrays for new values */
+ values = (Datum *) palloc(nitems * sizeof(Datum));
+ nulls = (bool *) palloc(nitems * sizeof(bool));
+
+ /* Loop over source data */
+ arraydataptr = ARR_DATA_PTR(array);
+ bitmap = ARR_NULLBITMAP(array);
+ bitmask = 1;
+ hasnulls = false;
+ nresult = 0;
+
+ for (i = 0; i < nitems; i++)
+ {
+ Datum elt;
+ bool isNull;
+ bool oprresult;
+ bool skip = false;
+
+ /* Get source element, checking for NULL */
+ if (bitmap && (*bitmap & bitmask) == 0)
+ {
+ isNull = true;
+ /* If searching for NULL, we have a match */
+ if (search_isnull)
+ {
+ if (remove)
+ {
+ skip = true;
+ changed = true;
+ }
+ else if (!replace_isnull)
+ {
+ values[nresult] = replace;
+ isNull = false;
+ changed = true;
+ }
+ }
+ }
+ else
+ {
+ isNull = false;
+ elt = fetch_att(arraydataptr, typbyval, typlen);
+ arraydataptr = att_addlength_datum(arraydataptr, typlen, elt);
+ arraydataptr = (char *) att_align_nominal(arraydataptr, typalign);
+
+ if (search_isnull)
+ {
+ /* no match possible, keep element */
+ values[nresult] = elt;
+ }
+ else
+ {
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = search;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (locfcinfo->isnull || !oprresult)
+ {
+ /* no match, keep element */
+ values[nresult] = elt;
+ }
+ else
+ {
+ /* match, so replace or delete */
+ changed = true;
+ if (remove)
+ skip = true;
+ else
+ {
+ values[nresult] = replace;
+ isNull = replace_isnull;
+ }
+ }
+ }
+ }
+
+ if (!skip)
+ {
+ nulls[nresult] = isNull;
+ if (isNull)
+ hasnulls = true;
+ else
+ {
+ /* Update total result size */
+ nbytes = att_addlength_datum(nbytes, typlen, values[nresult]);
+ nbytes = att_align_nominal(nbytes, typalign);
+ /* check for overflow of total request */
+ if (!AllocSizeIsValid(nbytes))
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("array size exceeds the maximum allowed (%d)",
+ (int) MaxAllocSize)));
+ }
+ nresult++;
+ }
+
+ /* advance bitmap pointer if any */
+ if (bitmap)
+ {
+ bitmask <<= 1;
+ if (bitmask == 0x100)
+ {
+ bitmap++;
+ bitmask = 1;
+ }
+ }
+ }
+
+ /*
+ * If not changed just return the original array
+ */
+ if (!changed)
+ {
+ pfree(values);
+ pfree(nulls);
+ return array;
+ }
+
+ /* If all elements were removed return an empty array */
+ if (nresult == 0)
+ {
+ pfree(values);
+ pfree(nulls);
+ return construct_empty_array(element_type);
+ }
+
+ /* Allocate and initialize the result array */
+ if (hasnulls)
+ {
+ dataoffset = ARR_OVERHEAD_WITHNULLS(ndim, nresult);
+ nbytes += dataoffset;
+ }
+ else
+ {
+ dataoffset = 0; /* marker for no null bitmap */
+ nbytes += ARR_OVERHEAD_NONULLS(ndim);
+ }
+ result = (ArrayType *) palloc0(nbytes);
+ SET_VARSIZE(result, nbytes);
+ result->ndim = ndim;
+ result->dataoffset = dataoffset;
+ result->elemtype = element_type;
+ memcpy(ARR_DIMS(result), ARR_DIMS(array), ndim * sizeof(int));
+ memcpy(ARR_LBOUND(result), ARR_LBOUND(array), ndim * sizeof(int));
+
+ if (remove)
+ {
+ /* Adjust the result length */
+ ARR_DIMS(result)[0] = nresult;
+ }
+
+ /* Insert data into result array */
+ CopyArrayEls(result,
+ values, nulls, nresult,
+ typlen, typbyval, typalign,
+ false);
+
+ pfree(values);
+ pfree(nulls);
+
+ return result;
+}
+
+/*
+ * Remove any occurrences of an element from an array
+ *
+ * If used on a multi-dimensional array this will raise an error.
+ */
+Datum
+array_remove(PG_FUNCTION_ARGS)
+{
+ ArrayType *array;
+ Datum search = PG_GETARG_DATUM(1);
+ bool search_isnull = PG_ARGISNULL(1);
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+ array = PG_GETARG_ARRAYTYPE_P(0);
+
+ array = array_replace_internal(array,
+ search, search_isnull,
+ (Datum) 0, true,
+ true, PG_GET_COLLATION(),
+ fcinfo);
+ PG_RETURN_ARRAYTYPE_P(array);
+}
+
+/*
+ * Replace any occurrences of an element in an array
+ */
+Datum
+array_replace(PG_FUNCTION_ARGS)
+{
+ ArrayType *array;
+ Datum search = PG_GETARG_DATUM(1);
+ bool search_isnull = PG_ARGISNULL(1);
+ Datum replace = PG_GETARG_DATUM(2);
+ bool replace_isnull = PG_ARGISNULL(2);
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+ array = PG_GETARG_ARRAYTYPE_P(0);
+
+ array = array_replace_internal(array,
+ search, search_isnull,
+ replace, replace_isnull,
+ false, PG_GET_COLLATION(),
+ fcinfo);
+ PG_RETURN_ARRAYTYPE_P(array);
+}
+
+/*
+ * Implements width_bucket(anyelement, anyarray).
+ *
+ * 'thresholds' is an array containing lower bound values for each bucket;
+ * these must be sorted from smallest to largest, or bogus results will be
+ * produced. If N thresholds are supplied, the output is from 0 to N:
+ * 0 is for inputs < first threshold, N is for inputs >= last threshold.
+ */
+Datum
+width_bucket_array(PG_FUNCTION_ARGS)
+{
+ Datum operand = PG_GETARG_DATUM(0);
+ ArrayType *thresholds = PG_GETARG_ARRAYTYPE_P(1);
+ Oid collation = PG_GET_COLLATION();
+ Oid element_type = ARR_ELEMTYPE(thresholds);
+ int result;
+
+ /* Check input */
+ if (ARR_NDIM(thresholds) > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("thresholds must be one-dimensional array")));
+
+ if (array_contains_nulls(thresholds))
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("thresholds array must not contain NULLs")));
+
+ /* We have a dedicated implementation for float8 data */
+ if (element_type == FLOAT8OID)
+ result = width_bucket_array_float8(operand, thresholds);
+ else
+ {
+ TypeCacheEntry *typentry;
+
+ /* Cache information about the input type */
+ typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != element_type)
+ {
+ typentry = lookup_type_cache(element_type,
+ TYPECACHE_CMP_PROC_FINFO);
+ if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify a comparison function for type %s",
+ format_type_be(element_type))));
+ fcinfo->flinfo->fn_extra = (void *) typentry;
+ }
+
+ /*
+ * We have separate implementation paths for fixed- and variable-width
+ * types, since indexing the array is a lot cheaper in the first case.
+ */
+ if (typentry->typlen > 0)
+ result = width_bucket_array_fixed(operand, thresholds,
+ collation, typentry);
+ else
+ result = width_bucket_array_variable(operand, thresholds,
+ collation, typentry);
+ }
+
+ /* Avoid leaking memory when handed toasted input. */
+ PG_FREE_IF_COPY(thresholds, 1);
+
+ PG_RETURN_INT32(result);
+}
+
+/*
+ * width_bucket_array for float8 data.
+ */
+static int
+width_bucket_array_float8(Datum operand, ArrayType *thresholds)
+{
+ float8 op = DatumGetFloat8(operand);
+ float8 *thresholds_data;
+ int left;
+ int right;
+
+ /*
+ * Since we know the array contains no NULLs, we can just index it
+ * directly.
+ */
+ thresholds_data = (float8 *) ARR_DATA_PTR(thresholds);
+
+ left = 0;
+ right = ArrayGetNItems(ARR_NDIM(thresholds), ARR_DIMS(thresholds));
+
+ /*
+ * If the probe value is a NaN, it's greater than or equal to all possible
+ * threshold values (including other NaNs), so we need not search. Note
+ * that this would give the same result as searching even if the array
+ * contains multiple NaNs (as long as they're correctly sorted), since the
+ * loop logic will find the rightmost of multiple equal threshold values.
+ */
+ if (isnan(op))
+ return right;
+
+ /* Find the bucket */
+ while (left < right)
+ {
+ int mid = (left + right) / 2;
+
+ if (isnan(thresholds_data[mid]) || op < thresholds_data[mid])
+ right = mid;
+ else
+ left = mid + 1;
+ }
+
+ return left;
+}
+
+/*
+ * width_bucket_array for generic fixed-width data types.
+ */
+static int
+width_bucket_array_fixed(Datum operand,
+ ArrayType *thresholds,
+ Oid collation,
+ TypeCacheEntry *typentry)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ char *thresholds_data;
+ int typlen = typentry->typlen;
+ bool typbyval = typentry->typbyval;
+ int left;
+ int right;
+
+ /*
+ * Since we know the array contains no NULLs, we can just index it
+ * directly.
+ */
+ thresholds_data = (char *) ARR_DATA_PTR(thresholds);
+
+ InitFunctionCallInfoData(*locfcinfo, &typentry->cmp_proc_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Find the bucket */
+ left = 0;
+ right = ArrayGetNItems(ARR_NDIM(thresholds), ARR_DIMS(thresholds));
+ while (left < right)
+ {
+ int mid = (left + right) / 2;
+ char *ptr;
+ int32 cmpresult;
+
+ ptr = thresholds_data + mid * typlen;
+
+ locfcinfo->args[0].value = operand;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = fetch_att(ptr, typbyval, typlen);
+ locfcinfo->args[1].isnull = false;
+
+ cmpresult = DatumGetInt32(FunctionCallInvoke(locfcinfo));
+
+ /* We don't expect comparison support functions to return null */
+ Assert(!locfcinfo->isnull);
+
+ if (cmpresult < 0)
+ right = mid;
+ else
+ left = mid + 1;
+ }
+
+ return left;
+}
+
+/*
+ * width_bucket_array for generic variable-width data types.
+ */
+static int
+width_bucket_array_variable(Datum operand,
+ ArrayType *thresholds,
+ Oid collation,
+ TypeCacheEntry *typentry)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ char *thresholds_data;
+ int typlen = typentry->typlen;
+ bool typbyval = typentry->typbyval;
+ char typalign = typentry->typalign;
+ int left;
+ int right;
+
+ thresholds_data = (char *) ARR_DATA_PTR(thresholds);
+
+ InitFunctionCallInfoData(*locfcinfo, &typentry->cmp_proc_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Find the bucket */
+ left = 0;
+ right = ArrayGetNItems(ARR_NDIM(thresholds), ARR_DIMS(thresholds));
+ while (left < right)
+ {
+ int mid = (left + right) / 2;
+ char *ptr;
+ int i;
+ int32 cmpresult;
+
+ /* Locate mid'th array element by advancing from left element */
+ ptr = thresholds_data;
+ for (i = left; i < mid; i++)
+ {
+ ptr = att_addlength_pointer(ptr, typlen, ptr);
+ ptr = (char *) att_align_nominal(ptr, typalign);
+ }
+
+ locfcinfo->args[0].value = operand;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = fetch_att(ptr, typbyval, typlen);
+ locfcinfo->args[1].isnull = false;
+
+ cmpresult = DatumGetInt32(FunctionCallInvoke(locfcinfo));
+
+ /* We don't expect comparison support functions to return null */
+ Assert(!locfcinfo->isnull);
+
+ if (cmpresult < 0)
+ right = mid;
+ else
+ {
+ left = mid + 1;
+
+ /*
+ * Move the thresholds pointer to match new "left" index, so we
+ * don't have to seek over those elements again. This trick
+ * ensures we do only O(N) array indexing work, not O(N^2).
+ */
+ ptr = att_addlength_pointer(ptr, typlen, ptr);
+ thresholds_data = (char *) att_align_nominal(ptr, typalign);
+ }
+ }
+
+ return left;
+}
+
+/*
+ * Trim the last N elements from an array by building an appropriate slice.
+ * Only the first dimension is trimmed.
+ */
+Datum
+trim_array(PG_FUNCTION_ARGS)
+{
+ ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
+ int n = PG_GETARG_INT32(1);
+ int array_length = (ARR_NDIM(v) > 0) ? ARR_DIMS(v)[0] : 0;
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
+ int lower[MAXDIM];
+ int upper[MAXDIM];
+ bool lowerProvided[MAXDIM];
+ bool upperProvided[MAXDIM];
+ Datum result;
+
+ /* Per spec, throw an error if out of bounds */
+ if (n < 0 || n > array_length)
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
+ errmsg("number of elements to trim must be between 0 and %d",
+ array_length)));
+
+ /* Set all the bounds as unprovided except the first upper bound */
+ memset(lowerProvided, false, sizeof(lowerProvided));
+ memset(upperProvided, false, sizeof(upperProvided));
+ if (ARR_NDIM(v) > 0)
+ {
+ upper[0] = ARR_LBOUND(v)[0] + array_length - n - 1;
+ upperProvided[0] = true;
+ }
+
+ /* Fetch the needed information about the element type */
+ get_typlenbyvalalign(ARR_ELEMTYPE(v), &elmlen, &elmbyval, &elmalign);
+
+ /* Get the slice */
+ result = array_get_slice(PointerGetDatum(v), 1,
+ upper, lower, upperProvided, lowerProvided,
+ -1, elmlen, elmbyval, elmalign);
+
+ PG_RETURN_DATUM(result);
+}