Permalink
Switch branches/tags
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
6558 lines (5901 sloc) 170 KB
/*-------------------------------------------------------------------------
*
* arrayfuncs.c
* Support functions for arrays.
*
* Portions Copyright (c) 1996-2018, 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/hash.h"
#include "access/htup_details.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "libpq/pqformat.h"
#include "utils/array.h"
#include "utils/arrayaccess.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.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 *lbv, 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);
/* 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;
int nitems,
overall_length,
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 = 1; /* don't forget to count \0 at end. */
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 */
overall_length += 1;
}
/*
* count total number of curly braces in output string
*/
for (i = j = 0, k = 1; i < ndim; i++)
k *= dims[i], j += k;
dims_str[0] = '\0';
/* add explicit dimensions if required */
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';
}
retval = (char *) palloc(strlen(dims_str) + overall_length + 2 * j);
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--)
{
indx[i] = (indx[i] + 1) % dims[i];
if (indx[i])
{
APPENDCHAR(typdelim);
break;
}
else
APPENDCHAR('}');
}
j = i;
} while (j != -1);
#undef APPENDSTR
#undef APPENDCHAR
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")));
element_type = pq_getmsgint(buf, sizeof(Oid));
if (element_type != spec_element_type)
{
/* XXX Can we allow taking the input element type in any cases? */
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("wrong element type")));
}
for (i = 0; i < ndim; i++)
{
dim[i] = pq_getmsgint(buf, 4);
lBound[i] = pq_getmsgint(buf, 4);
/*
* Check overflow of upper bound. (ArrayNItems() below checks that
* dim[i] >= 0)
*/
if (dim[i] != 0)
{
int ub = lBound[i] + dim[i] - 1;
if (lBound[i] > ub)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
}
}
/* This checks for overflow of array dimensions */
nitems = ArrayGetNItems(ndim, dim);
/*
* 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 (upperIndex 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[]. 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] * elmlen >= arraytyplen)
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")));
}
}
/*
* Compute sizes of items and areas to copy
*/
newnitems = ArrayGetNItems(ndim, dim);
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). 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")));
}
}
/* 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[]. 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);
/*
* 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)));
nelems = ArrayGetNItems(ndims, dims);
/* 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)
{
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;
FunctionCallInfoData locfcinfo;
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
*/
locfcinfo.arg[0] = elt1;
locfcinfo.arg[1] = elt2;
locfcinfo.argnull[0] = false;
locfcinfo.argnull[1] = false;
locfcinfo.isnull = false;
oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo));
if (!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)
{
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;
FunctionCallInfoData locfcinfo;
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 */