mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-08-21 18:23:24 +02:00
functions are not strict, they will be called (passing a NULL first parameter) during any attempt to input a NULL value of their datatype. Currently, all our input functions are strict and so this commit does not change any behavior. However, this will make it possible to build domain input functions that centralize checking of domain constraints, thereby closing numerous holes in our domain support, as per previous discussion. While at it, I took the opportunity to introduce convenience functions InputFunctionCall, OutputFunctionCall, etc to use in code that calls I/O functions. This eliminates a lot of grotty-looking casts, but the main motivation is to make it easier to grep for these places if we ever need to touch them again.
4217 lines
107 KiB
C
4217 lines
107 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* arrayfuncs.c
|
|
* Support functions for arrays.
|
|
*
|
|
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $PostgreSQL: pgsql/src/backend/utils/adt/arrayfuncs.c,v 1.128 2006/04/04 19:35:35 tgl Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include <ctype.h>
|
|
|
|
#include "access/tupmacs.h"
|
|
#include "catalog/catalog.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "libpq/pqformat.h"
|
|
#include "parser/parse_coerce.h"
|
|
#include "parser/parse_oper.h"
|
|
#include "utils/array.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/datum.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/syscache.h"
|
|
#include "utils/typcache.h"
|
|
|
|
|
|
/*
|
|
* GUC parameter
|
|
*/
|
|
bool Array_nulls = true;
|
|
|
|
/*
|
|
* Local definitions
|
|
*/
|
|
#define ASSGN "="
|
|
|
|
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;
|
|
|
|
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 void CopyArrayEls(ArrayType *array,
|
|
Datum *values, bool *nulls, int nitems,
|
|
int typlen, bool typbyval, char typalign,
|
|
bool freedata);
|
|
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 Datum array_type_length_coerce_internal(ArrayType *src,
|
|
int32 desttypmod,
|
|
bool isExplicit,
|
|
FmgrInfo *fmgr_info);
|
|
|
|
|
|
/*
|
|
* 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 (isspace((unsigned char) *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, MAXDIM)));
|
|
|
|
for (q = p; isdigit((unsigned char) *q) || (*q == '-') || (*q == '+'); q++);
|
|
if (q == p) /* no digits? */
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
errmsg("missing dimension value")));
|
|
|
|
if (*q == ':')
|
|
{
|
|
/* [m:n] format */
|
|
*q = '\0';
|
|
lBound[ndim] = atoi(p);
|
|
p = q + 1;
|
|
for (q = p; isdigit((unsigned char) *q) || (*q == '-') || (*q == '+'); q++);
|
|
if (q == p) /* no digits? */
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
errmsg("missing dimension value")));
|
|
}
|
|
else
|
|
{
|
|
/* [n] format */
|
|
lBound[ndim] = 1;
|
|
}
|
|
if (*q != ']')
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
errmsg("missing \"]\" in 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("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("missing assignment operator")));
|
|
p += strlen(ASSGN);
|
|
while (isspace((unsigned char) *p))
|
|
p++;
|
|
|
|
/*
|
|
* intuit dimensions from brace structure -- it better match what we
|
|
* were given
|
|
*/
|
|
if (*p != '{')
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
errmsg("array value must start with \"{\" or dimension information")));
|
|
ndim_braces = ArrayCount(p, dim_braces, typdelim);
|
|
if (ndim_braces != ndim)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
errmsg("array dimensions incompatible with array literal")));
|
|
for (i = 0; i < ndim; ++i)
|
|
{
|
|
if (dim[i] != dim_braces[i])
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
errmsg("array dimensions incompatible with array literal")));
|
|
}
|
|
}
|
|
|
|
#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 *) palloc(nbytes);
|
|
retval->size = 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);
|
|
pfree(string_save);
|
|
|
|
PG_RETURN_ARRAYTYPE_P(retval);
|
|
}
|
|
|
|
/*
|
|
* 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] = 0;
|
|
nelems_last[i] = 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)));
|
|
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)));
|
|
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)));
|
|
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)));
|
|
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)));
|
|
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, 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)));
|
|
parse_state = ARRAY_LEVEL_COMPLETED;
|
|
if (nest_level == 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
errmsg("malformed array literal: \"%s\"", str)));
|
|
nest_level--;
|
|
|
|
if ((nelems_last[nest_level] != 1) &&
|
|
(nelems[nest_level] != nelems_last[nest_level]))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
errmsg("multidimensional arrays must have "
|
|
"array expressions 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)));
|
|
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 (!isspace((unsigned char) *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)));
|
|
parse_state = ARRAY_ELEM_STARTED;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if (!itemdone)
|
|
ptr++;
|
|
}
|
|
temp[ndim - 1]++;
|
|
ptr++;
|
|
}
|
|
|
|
/* only whitespace is allowed after the closing brace */
|
|
while (*ptr)
|
|
{
|
|
if (!isspace((unsigned char) *ptr++))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
errmsg("malformed array literal: \"%s\"", str)));
|
|
}
|
|
|
|
/* 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.
|
|
*/
|
|
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 (isspace((unsigned char) *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(totbytes, typlen, values[i]);
|
|
totbytes = att_align(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...)
|
|
*/
|
|
static 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)
|
|
{
|
|
ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
|
|
Oid element_type = ARR_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
|
|
*/
|
|
bits8 *bitmap;
|
|
int bitmask;
|
|
bool *needquotes,
|
|
needdims = false;
|
|
int nitems,
|
|
overall_length,
|
|
i,
|
|
j,
|
|
k,
|
|
indx[MAXDIM];
|
|
int ndim,
|
|
*dims,
|
|
*lb;
|
|
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 = ARR_NDIM(v);
|
|
dims = ARR_DIMS(v);
|
|
lb = ARR_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. */
|
|
|
|
p = ARR_DATA_PTR(v);
|
|
bitmap = ARR_NULLBITMAP(v);
|
|
bitmask = 1;
|
|
|
|
for (i = 0; i < nitems; i++)
|
|
{
|
|
bool needquote;
|
|
|
|
/* Get source element, checking for NULL */
|
|
if (bitmap && (*bitmap & bitmask) == 0)
|
|
{
|
|
values[i] = pstrdup("NULL");
|
|
overall_length += 4;
|
|
needquote = false;
|
|
}
|
|
else
|
|
{
|
|
Datum itemvalue;
|
|
|
|
itemvalue = fetch_att(p, typbyval, typlen);
|
|
values[i] = OutputFunctionCall(&my_extra->proc, itemvalue);
|
|
p = att_addlength(p, typlen, PointerGetDatum(p));
|
|
p = (char *) att_align(p, typalign);
|
|
|
|
/* 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;
|
|
#ifndef TCL_ARRAYS
|
|
overall_length += 1;
|
|
#endif
|
|
}
|
|
else if (ch == '{' || ch == '}' || ch == typdelim ||
|
|
isspace((unsigned char) 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;
|
|
|
|
/* advance bitmap pointer if any */
|
|
if (bitmap)
|
|
{
|
|
bitmask <<= 1;
|
|
if (bitmask == 0x100)
|
|
{
|
|
bitmap++;
|
|
bitmask = 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('"');
|
|
#ifndef TCL_ARRAYS
|
|
for (tmp = values[k]; *tmp; tmp++)
|
|
{
|
|
char ch = *tmp;
|
|
|
|
if (ch == '"' || ch == '\\')
|
|
*p++ = '\\';
|
|
*p++ = ch;
|
|
}
|
|
*p = '\0';
|
|
#else
|
|
APPENDSTR(values[k]);
|
|
#endif
|
|
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);
|
|
}
|
|
|
|
/* 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 *) palloc(nbytes);
|
|
retval->size = 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(totbytes, typlen, values[i]);
|
|
totbytes = att_align(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)
|
|
{
|
|
ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
|
|
Oid element_type = ARR_ELEMTYPE(v);
|
|
int typlen;
|
|
bool typbyval;
|
|
char typalign;
|
|
char *p;
|
|
bits8 *bitmap;
|
|
int bitmask;
|
|
int nitems,
|
|
i;
|
|
int ndim,
|
|
*dim;
|
|
StringInfoData buf;
|
|
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 = ARR_NDIM(v);
|
|
dim = ARR_DIMS(v);
|
|
nitems = ArrayGetNItems(ndim, dim);
|
|
|
|
pq_begintypsend(&buf);
|
|
|
|
/* Send the array header information */
|
|
pq_sendint(&buf, ndim, 4);
|
|
pq_sendint(&buf, ARR_HASNULL(v) ? 1 : 0, 4);
|
|
pq_sendint(&buf, element_type, sizeof(Oid));
|
|
for (i = 0; i < ndim; i++)
|
|
{
|
|
pq_sendint(&buf, ARR_DIMS(v)[i], 4);
|
|
pq_sendint(&buf, ARR_LBOUND(v)[i], 4);
|
|
}
|
|
|
|
/* Send the array elements using the element's own sendproc */
|
|
p = ARR_DATA_PTR(v);
|
|
bitmap = ARR_NULLBITMAP(v);
|
|
bitmask = 1;
|
|
|
|
for (i = 0; i < nitems; i++)
|
|
{
|
|
/* Get source element, checking for NULL */
|
|
if (bitmap && (*bitmap & bitmask) == 0)
|
|
{
|
|
/* -1 length means a NULL */
|
|
pq_sendint(&buf, -1, 4);
|
|
}
|
|
else
|
|
{
|
|
Datum itemvalue;
|
|
bytea *outputbytes;
|
|
|
|
itemvalue = fetch_att(p, typbyval, typlen);
|
|
outputbytes = SendFunctionCall(&my_extra->proc, itemvalue);
|
|
pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
|
|
pq_sendbytes(&buf, VARDATA(outputbytes),
|
|
VARSIZE(outputbytes) - VARHDRSZ);
|
|
pfree(outputbytes);
|
|
|
|
p = att_addlength(p, typlen, PointerGetDatum(p));
|
|
p = (char *) att_align(p, typalign);
|
|
}
|
|
|
|
/* advance bitmap pointer if any */
|
|
if (bitmap)
|
|
{
|
|
bitmask <<= 1;
|
|
if (bitmask == 0x100)
|
|
{
|
|
bitmap++;
|
|
bitmask = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
|
|
}
|
|
|
|
/*
|
|
* array_dims :
|
|
* returns the dimensions of the array pointed to by "v", as a "text"
|
|
*/
|
|
Datum
|
|
array_dims(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
|
|
text *result;
|
|
char *p;
|
|
int nbytes,
|
|
i;
|
|
int *dimv,
|
|
*lb;
|
|
|
|
/* Sanity check: does it look like an array at all? */
|
|
if (ARR_NDIM(v) <= 0 || ARR_NDIM(v) > MAXDIM)
|
|
PG_RETURN_NULL();
|
|
|
|
nbytes = ARR_NDIM(v) * 33 + 1;
|
|
|
|
/*
|
|
* 33 since we assume 15 digits per number + ':' +'[]'
|
|
*
|
|
* +1 allows for temp trailing null
|
|
*/
|
|
|
|
result = (text *) palloc(nbytes + VARHDRSZ);
|
|
p = VARDATA(result);
|
|
|
|
dimv = ARR_DIMS(v);
|
|
lb = ARR_LBOUND(v);
|
|
|
|
for (i = 0; i < ARR_NDIM(v); i++)
|
|
{
|
|
sprintf(p, "[%d:%d]", lb[i], dimv[i] + lb[i] - 1);
|
|
p += strlen(p);
|
|
}
|
|
VARATT_SIZEP(result) = strlen(VARDATA(result)) + VARHDRSZ;
|
|
|
|
PG_RETURN_TEXT_P(result);
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
|
|
int reqdim = PG_GETARG_INT32(1);
|
|
int *lb;
|
|
int result;
|
|
|
|
/* Sanity check: does it look like an array at all? */
|
|
if (ARR_NDIM(v) <= 0 || ARR_NDIM(v) > MAXDIM)
|
|
PG_RETURN_NULL();
|
|
|
|
/* Sanity check: was the requested dim valid */
|
|
if (reqdim <= 0 || reqdim > ARR_NDIM(v))
|
|
PG_RETURN_NULL();
|
|
|
|
lb = ARR_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)
|
|
{
|
|
ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
|
|
int reqdim = PG_GETARG_INT32(1);
|
|
int *dimv,
|
|
*lb;
|
|
int result;
|
|
|
|
/* Sanity check: does it look like an array at all? */
|
|
if (ARR_NDIM(v) <= 0 || ARR_NDIM(v) > MAXDIM)
|
|
PG_RETURN_NULL();
|
|
|
|
/* Sanity check: was the requested dim valid */
|
|
if (reqdim <= 0 || reqdim > ARR_NDIM(v))
|
|
PG_RETURN_NULL();
|
|
|
|
lb = ARR_LBOUND(v);
|
|
dimv = ARR_DIMS(v);
|
|
|
|
result = dimv[reqdim - 1] + lb[reqdim - 1] - 1;
|
|
|
|
PG_RETURN_INT32(result);
|
|
}
|
|
|
|
/*
|
|
* array_ref :
|
|
* This routine takes an array pointer 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:
|
|
* array: 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_ref(ArrayType *array,
|
|
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 *) array;
|
|
arraynullsptr = NULL;
|
|
}
|
|
else
|
|
{
|
|
/* detoast input array if necessary */
|
|
array = DatumGetArrayTypeP(PointerGetDatum(array));
|
|
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* 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:
|
|
* array: 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
|
|
* 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)
|
|
*
|
|
* NOTE: we assume it is OK to scribble on the provided subscript arrays
|
|
* lowerIndx[] and upperIndx[]. These are generally just temporaries.
|
|
*/
|
|
ArrayType *
|
|
array_get_slice(ArrayType *array,
|
|
int nSubscripts,
|
|
int *upperIndx,
|
|
int *lowerIndx,
|
|
int arraytyplen,
|
|
int elmlen,
|
|
bool elmbyval,
|
|
char elmalign)
|
|
{
|
|
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 *) array;
|
|
arraynullsptr = NULL;
|
|
}
|
|
else
|
|
{
|
|
/* detoast input array if necessary */
|
|
array = DatumGetArrayTypeP(PointerGetDatum(array));
|
|
|
|
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 construct_empty_array(elemtype);
|
|
|
|
for (i = 0; i < nSubscripts; i++)
|
|
{
|
|
if (lowerIndx[i] < lb[i])
|
|
lowerIndx[i] = lb[i];
|
|
if (upperIndx[i] >= (dim[i] + lb[i]))
|
|
upperIndx[i] = dim[i] + lb[i] - 1;
|
|
if (lowerIndx[i] > upperIndx[i])
|
|
return 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 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 *) palloc(bytes);
|
|
newarray->size = 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 newarray;
|
|
}
|
|
|
|
/*
|
|
* array_set :
|
|
* This routine sets the value of an 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:
|
|
* array: 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.
|
|
*
|
|
* For one-dimensional arrays only, we allow the array to be extended
|
|
* by assigning to the position one above or one below the existing range.
|
|
* (XXX we could be more flexible: perhaps allow NULL fill?)
|
|
*
|
|
* NOTE: For assignments, we throw an error for invalid subscripts etc,
|
|
* rather than returning a NULL as the fetch operations do.
|
|
*/
|
|
ArrayType *
|
|
array_set(ArrayType *array,
|
|
int nSubscripts,
|
|
int *indx,
|
|
Datum dataValue,
|
|
bool isNull,
|
|
int arraytyplen,
|
|
int elmlen,
|
|
bool elmbyval,
|
|
char elmalign)
|
|
{
|
|
ArrayType *newarray;
|
|
int i,
|
|
ndim,
|
|
dim[MAXDIM],
|
|
lb[MAXDIM],
|
|
offset;
|
|
char *elt_ptr;
|
|
bool extendbefore = false;
|
|
bool extendafter = false;
|
|
bool newhasnulls;
|
|
bits8 *oldnullbitmap;
|
|
int oldnitems,
|
|
olddatasize,
|
|
newsize,
|
|
olditemlen,
|
|
newitemlen,
|
|
overheadlen,
|
|
oldoverheadlen,
|
|
lenbefore,
|
|
lenafter;
|
|
|
|
if (arraytyplen > 0)
|
|
{
|
|
/*
|
|
* fixed-length arrays -- these are assumed to be 1-d, 0-based. We
|
|
* cannot extend them, either.
|
|
*/
|
|
if (nSubscripts != 1)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
|
errmsg("invalid array subscripts")));
|
|
|
|
if (indx[0] < 0 || indx[0] * elmlen >= arraytyplen)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
|
errmsg("invalid array subscripts")));
|
|
|
|
if (isNull)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
errmsg("cannot assign NULL to an element of a fixed-length array")));
|
|
|
|
newarray = (ArrayType *) palloc(arraytyplen);
|
|
memcpy(newarray, array, arraytyplen);
|
|
elt_ptr = (char *) newarray + indx[0] * elmlen;
|
|
ArrayCastAndSet(dataValue, elmlen, elmbyval, elmalign, elt_ptr);
|
|
return newarray;
|
|
}
|
|
|
|
if (nSubscripts <= 0 || nSubscripts > MAXDIM)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
|
errmsg("invalid array subscripts")));
|
|
|
|
/* make sure item to be inserted is not toasted */
|
|
if (elmlen == -1 && !isNull)
|
|
dataValue = PointerGetDatum(PG_DETOAST_DATUM(dataValue));
|
|
|
|
/* detoast input array if necessary */
|
|
array = DatumGetArrayTypeP(PointerGetDatum(array));
|
|
|
|
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 construct_md_array(&dataValue, &isNull, nSubscripts,
|
|
dim, lb, elmtype,
|
|
elmlen, elmbyval, elmalign);
|
|
}
|
|
|
|
if (ndim != nSubscripts)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
|
errmsg("invalid 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));
|
|
|
|
/*
|
|
* Check subscripts
|
|
*/
|
|
for (i = 0; i < ndim; i++)
|
|
{
|
|
if (indx[i] < lb[i])
|
|
{
|
|
if (ndim == 1 && indx[i] == lb[i] - 1)
|
|
{
|
|
dim[i]++;
|
|
lb[i]--;
|
|
extendbefore = true;
|
|
}
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
|
errmsg("invalid array subscripts")));
|
|
}
|
|
if (indx[i] >= (dim[i] + lb[i]))
|
|
{
|
|
if (ndim == 1 && indx[i] == (dim[i] + lb[i]))
|
|
{
|
|
dim[i]++;
|
|
extendafter = true;
|
|
}
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
|
errmsg("invalid array subscripts")));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Compute sizes of items and areas to copy
|
|
*/
|
|
if (ARR_HASNULL(array) || isNull)
|
|
{
|
|
newhasnulls = true;
|
|
overheadlen = ARR_OVERHEAD_WITHNULLS(ndim,
|
|
ArrayGetNItems(ndim, dim));
|
|
}
|
|
else
|
|
{
|
|
newhasnulls = false;
|
|
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 (extendbefore)
|
|
{
|
|
offset = 0;
|
|
lenbefore = 0;
|
|
olditemlen = 0;
|
|
lenafter = olddatasize;
|
|
}
|
|
else if (extendafter)
|
|
{
|
|
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(0, elmlen, PointerGetDatum(elt_ptr));
|
|
olditemlen = att_align(olditemlen, elmalign);
|
|
}
|
|
lenafter = (int) (olddatasize - lenbefore - olditemlen);
|
|
}
|
|
|
|
if (isNull)
|
|
newitemlen = 0;
|
|
else
|
|
{
|
|
newitemlen = att_addlength(0, elmlen, dataValue);
|
|
newitemlen = att_align(newitemlen, elmalign);
|
|
}
|
|
|
|
newsize = overheadlen + lenbefore + newitemlen + lenafter;
|
|
|
|
/*
|
|
* OK, create the new array and fill in header/dimensions
|
|
*/
|
|
newarray = (ArrayType *) palloc(newsize);
|
|
newarray->size = 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);
|
|
|
|
array_set_isnull(newnullbitmap, offset, isNull);
|
|
if (extendbefore)
|
|
array_bitmap_copy(newnullbitmap, 1,
|
|
oldnullbitmap, 0,
|
|
oldnitems);
|
|
else
|
|
{
|
|
array_bitmap_copy(newnullbitmap, 0,
|
|
oldnullbitmap, 0,
|
|
offset);
|
|
if (!extendafter)
|
|
array_bitmap_copy(newnullbitmap, offset + 1,
|
|
oldnullbitmap, offset + 1,
|
|
oldnitems - offset - 1);
|
|
}
|
|
}
|
|
|
|
return newarray;
|
|
}
|
|
|
|
/*
|
|
* 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:
|
|
* array: 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
|
|
* srcArray: the source for the inserted values
|
|
* isNull: indicates whether srcArray 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.
|
|
*
|
|
* 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.
|
|
*/
|
|
ArrayType *
|
|
array_set_slice(ArrayType *array,
|
|
int nSubscripts,
|
|
int *upperIndx,
|
|
int *lowerIndx,
|
|
ArrayType *srcArray,
|
|
bool isNull,
|
|
int arraytyplen,
|
|
int elmlen,
|
|
bool elmbyval,
|
|
char elmalign)
|
|
{
|
|
ArrayType *newarray;
|
|
int i,
|
|
ndim,
|
|
dim[MAXDIM],
|
|
lb[MAXDIM],
|
|
span[MAXDIM];
|
|
bool newhasnulls;
|
|
int nitems,
|
|
nsrcitems,
|
|
olddatasize,
|
|
newsize,
|
|
olditemsize,
|
|
newitemsize,
|
|
overheadlen,
|
|
oldoverheadlen,
|
|
lenbefore,
|
|
lenafter,
|
|
itemsbefore,
|
|
itemsafter,
|
|
nolditems;
|
|
|
|
/* Currently, assignment from a NULL source array is a no-op */
|
|
if (isNull)
|
|
return array;
|
|
|
|
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(PointerGetDatum(array));
|
|
srcArray = DatumGetArrayTypeP(PointerGetDatum(srcArray));
|
|
|
|
/* 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++)
|
|
{
|
|
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 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("invalid 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));
|
|
|
|
/*
|
|
* Check provided subscripts. A slice exceeding the current array limits
|
|
* throws an error, *except* in the 1-D case where we will extend the
|
|
* array as long as no hole is created. An empty slice is an error, too.
|
|
*/
|
|
for (i = 0; i < nSubscripts; i++)
|
|
{
|
|
if (lowerIndx[i] > upperIndx[i])
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
|
errmsg("invalid array subscripts")));
|
|
if (lowerIndx[i] < lb[i])
|
|
{
|
|
if (ndim == 1 && upperIndx[i] >= lb[i] - 1)
|
|
{
|
|
dim[i] += lb[i] - lowerIndx[i];
|
|
lb[i] = lowerIndx[i];
|
|
}
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
|
errmsg("invalid array subscripts")));
|
|
}
|
|
if (upperIndx[i] >= (dim[i] + lb[i]))
|
|
{
|
|
if (ndim == 1 && lowerIndx[i] <= (dim[i] + lb[i]))
|
|
dim[i] = upperIndx[i] - lb[i] + 1;
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
|
errmsg("invalid array subscripts")));
|
|
}
|
|
}
|
|
/* 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("invalid array subscripts")));
|
|
}
|
|
|
|
/* 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 (ARR_HASNULL(array) || ARR_HASNULL(srcArray))
|
|
{
|
|
newhasnulls = true;
|
|
overheadlen = ARR_OVERHEAD_WITHNULLS(ndim, nitems);
|
|
}
|
|
else
|
|
{
|
|
newhasnulls = false;
|
|
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
|
|
*/
|
|
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);
|
|
|
|
itemsbefore = slicelb - oldlb;
|
|
lenbefore = array_nelems_size(oldarraydata, 0, oldarraybitmap,
|
|
itemsbefore,
|
|
elmlen, elmbyval, elmalign);
|
|
if (slicelb > sliceub)
|
|
{
|
|
nolditems = 0;
|
|
olditemsize = 0;
|
|
}
|
|
else
|
|
{
|
|
nolditems = sliceub - slicelb + 1;
|
|
olditemsize = array_nelems_size(oldarraydata + lenbefore,
|
|
itemsbefore, oldarraybitmap,
|
|
nolditems,
|
|
elmlen, elmbyval, elmalign);
|
|
}
|
|
itemsafter = oldub - sliceub;
|
|
lenafter = olddatasize - lenbefore - olditemsize;
|
|
}
|
|
|
|
newsize = overheadlen + olddatasize - olditemsize + newitemsize;
|
|
|
|
newarray = (ArrayType *) palloc(newsize);
|
|
newarray->size = 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);
|
|
|
|
array_bitmap_copy(newnullbitmap, 0,
|
|
oldnullbitmap, 0,
|
|
itemsbefore);
|
|
array_bitmap_copy(newnullbitmap, itemsbefore,
|
|
ARR_NULLBITMAP(srcArray), 0,
|
|
nsrcitems);
|
|
array_bitmap_copy(newnullbitmap, itemsbefore + nsrcitems,
|
|
oldnullbitmap, itemsbefore + nolditems,
|
|
itemsafter);
|
|
}
|
|
}
|
|
|
|
return newarray;
|
|
}
|
|
|
|
/*
|
|
* array_map()
|
|
*
|
|
* Map an array through an arbitrary function. Return a new array with
|
|
* same dimensions and each source element transformed by fn(). Each
|
|
* source element is passed as the first argument to fn(); additional
|
|
* arguments to be passed to fn() can be specified by the caller.
|
|
* The output array can have a different element type than the input.
|
|
*
|
|
* Parameters are:
|
|
* * fcinfo: a function-call data structure pre-constructed by the caller
|
|
* to be ready to call the desired function, with everything except the
|
|
* first argument position filled in. In particular, flinfo identifies
|
|
* the function fn(), and if nargs > 1 then argument positions after the
|
|
* first must be preset to the additional values to be passed. The
|
|
* first argument position initially holds the input array value.
|
|
* * inpType: OID of element type of input array. This must be the same as,
|
|
* or binary-compatible with, the first argument type of fn().
|
|
* * retType: OID of element type of output array. This must be the same as,
|
|
* or binary-compatible with, the result type of fn().
|
|
* * 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.
|
|
*/
|
|
Datum
|
|
array_map(FunctionCallInfo fcinfo, Oid inpType, Oid retType,
|
|
ArrayMapState *amstate)
|
|
{
|
|
ArrayType *v;
|
|
ArrayType *result;
|
|
Datum *values;
|
|
bool *nulls;
|
|
Datum elt;
|
|
int *dim;
|
|
int ndim;
|
|
int nitems;
|
|
int i;
|
|
int32 nbytes = 0;
|
|
int32 dataoffset;
|
|
bool hasnulls;
|
|
int inp_typlen;
|
|
bool inp_typbyval;
|
|
char inp_typalign;
|
|
int typlen;
|
|
bool typbyval;
|
|
char typalign;
|
|
char *s;
|
|
bits8 *bitmap;
|
|
int bitmask;
|
|
ArrayMetaState *inp_extra;
|
|
ArrayMetaState *ret_extra;
|
|
|
|
/* Get input array */
|
|
if (fcinfo->nargs < 1)
|
|
elog(ERROR, "invalid nargs: %d", fcinfo->nargs);
|
|
if (PG_ARGISNULL(0))
|
|
elog(ERROR, "null input array");
|
|
v = PG_GETARG_ARRAYTYPE_P(0);
|
|
|
|
Assert(ARR_ELEMTYPE(v) == inpType);
|
|
|
|
ndim = ARR_NDIM(v);
|
|
dim = ARR_DIMS(v);
|
|
nitems = ArrayGetNItems(ndim, dim);
|
|
|
|
/* Check for empty array */
|
|
if (nitems <= 0)
|
|
{
|
|
/* Return empty array */
|
|
PG_RETURN_ARRAYTYPE_P(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 */
|
|
s = ARR_DATA_PTR(v);
|
|
bitmap = ARR_NULLBITMAP(v);
|
|
bitmask = 1;
|
|
hasnulls = false;
|
|
|
|
for (i = 0; i < nitems; i++)
|
|
{
|
|
bool callit = true;
|
|
|
|
/* Get source element, checking for NULL */
|
|
if (bitmap && (*bitmap & bitmask) == 0)
|
|
{
|
|
fcinfo->argnull[0] = true;
|
|
}
|
|
else
|
|
{
|
|
elt = fetch_att(s, inp_typbyval, inp_typlen);
|
|
s = att_addlength(s, inp_typlen, elt);
|
|
s = (char *) att_align(s, inp_typalign);
|
|
fcinfo->arg[0] = elt;
|
|
fcinfo->argnull[0] = false;
|
|
}
|
|
|
|
/*
|
|
* Apply the given function to source elt and extra args.
|
|
*/
|
|
if (fcinfo->flinfo->fn_strict)
|
|
{
|
|
int j;
|
|
|
|
for (j = 0; j < fcinfo->nargs; j++)
|
|
{
|
|
if (fcinfo->argnull[j])
|
|
{
|
|
callit = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (callit)
|
|
{
|
|
fcinfo->isnull = false;
|
|
values[i] = FunctionCallInvoke(fcinfo);
|
|
}
|
|
else
|
|
fcinfo->isnull = true;
|
|
|
|
nulls[i] = fcinfo->isnull;
|
|
if (fcinfo->isnull)
|
|
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(nbytes, typlen, values[i]);
|
|
nbytes = att_align(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)));
|
|
}
|
|
|
|
/* advance bitmap pointer if any */
|
|
if (bitmap)
|
|
{
|
|
bitmask <<= 1;
|
|
if (bitmask == 0x100)
|
|
{
|
|
bitmap++;
|
|
bitmask = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Allocate and initialize 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 *) palloc(nbytes);
|
|
result->size = nbytes;
|
|
result->ndim = ndim;
|
|
result->dataoffset = dataoffset;
|
|
result->elemtype = retType;
|
|
memcpy(ARR_DIMS(result), ARR_DIMS(v), 2 * ndim * sizeof(int));
|
|
|
|
/*
|
|
* Note: do not risk trying to pfree the results of the called function
|
|
*/
|
|
CopyArrayEls(result,
|
|
values, nulls, nitems,
|
|
typlen, typbyval, typalign,
|
|
false);
|
|
|
|
pfree(values);
|
|
pfree(nulls);
|
|
|
|
PG_RETURN_ARRAYTYPE_P(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.
|
|
*
|
|
* 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.
|
|
*
|
|
* 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)));
|
|
|
|
/* fast track for empty array */
|
|
if (ndims == 0)
|
|
return construct_empty_array(elmtype);
|
|
|
|
nelems = ArrayGetNItems(ndims, dims);
|
|
|
|
/* 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(nbytes, elmlen, elems[i]);
|
|
nbytes = att_align(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 *) palloc(nbytes);
|
|
result->size = 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 *) palloc(sizeof(ArrayType));
|
|
result->size = sizeof(ArrayType);
|
|
result->ndim = 0;
|
|
result->dataoffset = 0;
|
|
result->elemtype = elmtype;
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* 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 *) palloc(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);
|
|
if (nulls)
|
|
nulls[i] = false;
|
|
p = att_addlength(p, elmlen, PointerGetDatum(p));
|
|
p = (char *) att_align(p, elmalign);
|
|
}
|
|
|
|
/* advance bitmap pointer if any */
|
|
if (bitmap)
|
|
{
|
|
bitmask <<= 1;
|
|
if (bitmask == 0x100)
|
|
{
|
|
bitmap++;
|
|
bitmask = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
ArrayType *array1 = PG_GETARG_ARRAYTYPE_P(0);
|
|
ArrayType *array2 = PG_GETARG_ARRAYTYPE_P(1);
|
|
int ndims1 = ARR_NDIM(array1);
|
|
int ndims2 = ARR_NDIM(array2);
|
|
int *dims1 = ARR_DIMS(array1);
|
|
int *dims2 = ARR_DIMS(array2);
|
|
Oid element_type = ARR_ELEMTYPE(array1);
|
|
bool result = true;
|
|
int nitems;
|
|
TypeCacheEntry *typentry;
|
|
int typlen;
|
|
bool typbyval;
|
|
char typalign;
|
|
char *ptr1;
|
|
char *ptr2;
|
|
bits8 *bitmap1;
|
|
bits8 *bitmap2;
|
|
int bitmask;
|
|
int i;
|
|
FunctionCallInfoData locfcinfo;
|
|
|
|
if (element_type != ARR_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, 2 * 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,
|
|
NULL, NULL);
|
|
|
|
/* Loop over source data */
|
|
nitems = ArrayGetNItems(ndims1, dims1);
|
|
ptr1 = ARR_DATA_PTR(array1);
|
|
ptr2 = ARR_DATA_PTR(array2);
|
|
bitmap1 = ARR_NULLBITMAP(array1);
|
|
bitmap2 = ARR_NULLBITMAP(array2);
|
|
bitmask = 1; /* use same bitmask for both arrays */
|
|
|
|
for (i = 0; i < nitems; i++)
|
|
{
|
|
Datum elt1;
|
|
Datum elt2;
|
|
bool isnull1;
|
|
bool isnull2;
|
|
bool oprresult;
|
|
|
|
/* Get elements, checking for NULL */
|
|
if (bitmap1 && (*bitmap1 & bitmask) == 0)
|
|
{
|
|
isnull1 = true;
|
|
elt1 = (Datum) 0;
|
|
}
|
|
else
|
|
{
|
|
isnull1 = false;
|
|
elt1 = fetch_att(ptr1, typbyval, typlen);
|
|
ptr1 = att_addlength(ptr1, typlen, PointerGetDatum(ptr1));
|
|
ptr1 = (char *) att_align(ptr1, typalign);
|
|
}
|
|
|
|
if (bitmap2 && (*bitmap2 & bitmask) == 0)
|
|
{
|
|
isnull2 = true;
|
|
elt2 = (Datum) 0;
|
|
}
|
|
else
|
|
{
|
|
isnull2 = false;
|
|
elt2 = fetch_att(ptr2, typbyval, typlen);
|
|
ptr2 = att_addlength(ptr2, typlen, PointerGetDatum(ptr2));
|
|
ptr2 = (char *) att_align(ptr2, typalign);
|
|
}
|
|
|
|
/* advance bitmap pointers if any */
|
|
bitmask <<= 1;
|
|
if (bitmask == 0x100)
|
|
{
|
|
if (bitmap1)
|
|
bitmap1++;
|
|
if (bitmap2)
|
|
bitmap2++;
|
|
bitmask = 1;
|
|
}
|
|
|
|
/*
|
|
* 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. */
|
|
PG_FREE_IF_COPY(array1, 0);
|
|
PG_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)
|
|
{
|
|
ArrayType *array1 = PG_GETARG_ARRAYTYPE_P(0);
|
|
ArrayType *array2 = PG_GETARG_ARRAYTYPE_P(1);
|
|
int ndims1 = ARR_NDIM(array1);
|
|
int ndims2 = ARR_NDIM(array2);
|
|
int *dims1 = ARR_DIMS(array1);
|
|
int *dims2 = ARR_DIMS(array2);
|
|
int nitems1 = ArrayGetNItems(ndims1, dims1);
|
|
int nitems2 = ArrayGetNItems(ndims2, dims2);
|
|
Oid element_type = ARR_ELEMTYPE(array1);
|
|
int result = 0;
|
|
TypeCacheEntry *typentry;
|
|
int typlen;
|
|
bool typbyval;
|
|
char typalign;
|
|
int min_nitems;
|
|
char *ptr1;
|
|
char *ptr2;
|
|
bits8 *bitmap1;
|
|
bits8 *bitmap2;
|
|
int bitmask;
|
|
int i;
|
|
FunctionCallInfoData locfcinfo;
|
|
|
|
if (element_type != ARR_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,
|
|
NULL, NULL);
|
|
|
|
/* Loop over source data */
|
|
min_nitems = Min(nitems1, nitems2);
|
|
ptr1 = ARR_DATA_PTR(array1);
|
|
ptr2 = ARR_DATA_PTR(array2);
|
|
bitmap1 = ARR_NULLBITMAP(array1);
|
|
bitmap2 = ARR_NULLBITMAP(array2);
|
|
bitmask = 1; /* use same bitmask for both arrays */
|
|
|
|
for (i = 0; i < min_nitems; i++)
|
|
{
|
|
Datum elt1;
|
|
Datum elt2;
|
|
bool isnull1;
|
|
bool isnull2;
|
|
int32 cmpresult;
|
|
|
|
/* Get elements, checking for NULL */
|
|
if (bitmap1 && (*bitmap1 & bitmask) == 0)
|
|
{
|
|
isnull1 = true;
|
|
elt1 = (Datum) 0;
|
|
}
|
|
else
|
|
{
|
|
isnull1 = false;
|
|
elt1 = fetch_att(ptr1, typbyval, typlen);
|
|
ptr1 = att_addlength(ptr1, typlen, PointerGetDatum(ptr1));
|
|
ptr1 = (char *) att_align(ptr1, typalign);
|
|
}
|
|
|
|
if (bitmap2 && (*bitmap2 & bitmask) == 0)
|
|
{
|
|
isnull2 = true;
|
|
elt2 = (Datum) 0;
|
|
}
|
|
else
|
|
{
|
|
isnull2 = false;
|
|
elt2 = fetch_att(ptr2, typbyval, typlen);
|
|
ptr2 = att_addlength(ptr2, typlen, PointerGetDatum(ptr2));
|
|
ptr2 = (char *) att_align(ptr2, typalign);
|
|
}
|
|
|
|
/* advance bitmap pointers if any */
|
|
bitmask <<= 1;
|
|
if (bitmask == 0x100)
|
|
{
|
|
if (bitmap1)
|
|
bitmap1++;
|
|
if (bitmap2)
|
|
bitmap2++;
|
|
bitmask = 1;
|
|
}
|
|
|
|
/*
|
|
* We consider two NULLs equal; NULL > not-NULL.
|
|
*/
|
|
if (isnull1 && isnull2)
|
|
continue;
|
|
if (isnull1)
|
|
{
|
|
/* arg1 is greater than arg2 */
|
|
result = 1;
|
|
break;
|
|
}
|
|
if (isnull2)
|
|
{
|
|
/* arg1 is less than arg2 */
|
|
result = -1;
|
|
break;
|
|
}
|
|
|
|
/* Compare the pair of elements */
|
|
locfcinfo.arg[0] = elt1;
|
|
locfcinfo.arg[1] = elt2;
|
|
locfcinfo.argnull[0] = false;
|
|
locfcinfo.argnull[1] = false;
|
|
locfcinfo.isnull = false;
|
|
cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo));
|
|
|
|
if (cmpresult == 0)
|
|
continue; /* equal */
|
|
|
|
if (cmpresult < 0)
|
|
{
|
|
/* arg1 is less than arg2 */
|
|
result = -1;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
/* arg1 is greater than arg2 */
|
|
result = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If arrays contain same data (up to end of shorter one), apply
|
|
* additional rules to sort by dimensionality. The relative significance
|
|
* of the different bits of information is historical; mainly we just care
|
|
* that we don't say "equal" for arrays of different dimensionality.
|
|
*/
|
|
if (result == 0)
|
|
{
|
|
if (nitems1 != nitems2)
|
|
result = (nitems1 < nitems2) ? -1 : 1;
|
|
else if (ndims1 != ndims2)
|
|
result = (ndims1 < ndims2) ? -1 : 1;
|
|
else
|
|
{
|
|
/* this relies on LB array immediately following DIMS array */
|
|
for (i = 0; i < ndims1 * 2; i++)
|
|
{
|
|
if (dims1[i] != dims2[i])
|
|
{
|
|
result = (dims1[i] < dims2[i]) ? -1 : 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Avoid leaking memory when handed toasted input. */
|
|
PG_FREE_IF_COPY(array1, 0);
|
|
PG_FREE_IF_COPY(array2, 1);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/***************************************************************************/
|
|
/******************| Support Routines |*****************/
|
|
/***************************************************************************/
|
|
|
|
/*
|
|
* Check whether a specific array element is NULL
|
|
*
|
|
* nullbitmap: pointer to array's null bitmap (NULL if none)
|
|
* offset: 0-based linear element number of array element
|
|
*/
|
|
static bool
|
|
array_get_isnull(const bits8 *nullbitmap, int offset)
|
|
{
|
|
if (nullbitmap == NULL)
|
|
return false; /* assume not null */
|
|
if (nullbitmap[offset / 8] & (1 << (offset % 8)))
|
|
return false; /* not null */
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Set a specific array element's null-bitmap entry
|
|
*
|
|
* nullbitmap: pointer to array's null bitmap (mustn't be NULL)
|
|
* offset: 0-based linear element number of array element
|
|
* isNull: null status to set
|
|
*/
|
|
static void
|
|
array_set_isnull(bits8 *nullbitmap, int offset, bool isNull)
|
|
{
|
|
int bitmask;
|
|
|
|
nullbitmap += offset / 8;
|
|
bitmask = 1 << (offset % 8);
|
|
if (isNull)
|
|
*nullbitmap &= ~bitmask;
|
|
else
|
|
*nullbitmap |= bitmask;
|
|
}
|
|
|
|
/*
|
|
* Fetch array element at pointer, converted correctly to a Datum
|
|
*
|
|
* Caller must have handled case of NULL element
|
|
*/
|
|
static Datum
|
|
ArrayCast(char *value, bool byval, int len)
|
|
{
|
|
return fetch_att(value, byval, len);
|
|
}
|
|
|
|
/*
|
|
* Copy datum to *dest and return total space used (including align padding)
|
|
*
|
|
* Caller must have handled case of NULL element
|
|
*/
|
|
static int
|
|
ArrayCastAndSet(Datum src,
|
|
int typlen,
|
|
bool typbyval,
|
|
char typalign,
|
|
char *dest)
|
|
{
|
|
int inc;
|
|
|
|
if (typlen > 0)
|
|
{
|
|
if (typbyval)
|
|
store_att_byval(dest, src, typlen);
|
|
else
|
|
memmove(dest, DatumGetPointer(src), typlen);
|
|
inc = att_align(typlen, typalign);
|
|
}
|
|
else
|
|
{
|
|
Assert(!typbyval);
|
|
inc = att_addlength(0, typlen, src);
|
|
memmove(dest, DatumGetPointer(src), inc);
|
|
inc = att_align(inc, typalign);
|
|
}
|
|
|
|
return inc;
|
|
}
|
|
|
|
/*
|
|
* Advance ptr over nitems array elements
|
|
*
|
|
* ptr: starting location in array
|
|
* offset: 0-based linear element number of first element (the one at *ptr)
|
|
* nullbitmap: start of array's null bitmap, or NULL if none
|
|
* nitems: number of array elements to advance over (>= 0)
|
|
* typlen, typbyval, typalign: storage parameters of array element datatype
|
|
*
|
|
* It is caller's responsibility to ensure that nitems is within range
|
|
*/
|
|
static char *
|
|
array_seek(char *ptr, int offset, bits8 *nullbitmap, int nitems,
|
|
int typlen, bool typbyval, char typalign)
|
|
{
|
|
int bitmask;
|
|
int i;
|
|
|
|
/* easy if fixed-size elements and no NULLs */
|
|
if (typlen > 0 && !nullbitmap)
|
|
return ptr + nitems * ((Size) att_align(typlen, typalign));
|
|
|
|
/* seems worth having separate loops for NULL and no-NULLs cases */
|
|
if (nullbitmap)
|
|
{
|
|
nullbitmap += offset / 8;
|
|
bitmask = 1 << (offset % 8);
|
|
|
|
for (i = 0; i < nitems; i++)
|
|
{
|
|
if (*nullbitmap & bitmask)
|
|
{
|
|
ptr = att_addlength(ptr, typlen, PointerGetDatum(ptr));
|
|
ptr = (char *) att_align(ptr, typalign);
|
|
}
|
|
bitmask <<= 1;
|
|
if (bitmask == 0x100)
|
|
{
|
|
nullbitmap++;
|
|
bitmask = 1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < nitems; i++)
|
|
{
|
|
ptr = att_addlength(ptr, typlen, PointerGetDatum(ptr));
|
|
ptr = (char *) att_align(ptr, typalign);
|
|
}
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
/*
|
|
* Compute total size of the nitems array elements starting at *ptr
|
|
*
|
|
* Parameters same as for array_seek
|
|
*/
|
|
static int
|
|
array_nelems_size(char *ptr, int offset, bits8 *nullbitmap, int nitems,
|
|
int typlen, bool typbyval, char typalign)
|
|
{
|
|
return array_seek(ptr, offset, nullbitmap, nitems,
|
|
typlen, typbyval, typalign) - ptr;
|
|
}
|
|
|
|
/*
|
|
* Copy nitems array elements from srcptr to destptr
|
|
*
|
|
* destptr: starting destination location (must be enough room!)
|
|
* nitems: number of array elements to copy (>= 0)
|
|
* srcptr: starting location in source array
|
|
* offset: 0-based linear element number of first element (the one at *srcptr)
|
|
* nullbitmap: start of source array's null bitmap, or NULL if none
|
|
* typlen, typbyval, typalign: storage parameters of array element datatype
|
|
*
|
|
* Returns number of bytes copied
|
|
*
|
|
* NB: this does not take care of setting up the destination's null bitmap!
|
|
*/
|
|
static int
|
|
array_copy(char *destptr, int nitems,
|
|
char *srcptr, int offset, bits8 *nullbitmap,
|
|
int typlen, bool typbyval, char typalign)
|
|
{
|
|
int numbytes;
|
|
|
|
numbytes = array_nelems_size(srcptr, offset, nullbitmap, nitems,
|
|
typlen, typbyval, typalign);
|
|
memcpy(destptr, srcptr, numbytes);
|
|
return numbytes;
|
|
}
|
|
|
|
/*
|
|
* Copy nitems null-bitmap bits from source to destination
|
|
*
|
|
* destbitmap: start of destination array's null bitmap (mustn't be NULL)
|
|
* destoffset: 0-based linear element number of first dest element
|
|
* srcbitmap: start of source array's null bitmap, or NULL if none
|
|
* srcoffset: 0-based linear element number of first source element
|
|
* nitems: number of bits to copy (>= 0)
|
|
*
|
|
* If srcbitmap is NULL then we assume the source is all-non-NULL and
|
|
* fill 1's into the destination bitmap. Note that only the specified
|
|
* bits in the destination map are changed, not any before or after.
|
|
*
|
|
* Note: this could certainly be optimized using standard bitblt methods.
|
|
* However, it's not clear that the typical Postgres array has enough elements
|
|
* to make it worth worrying too much. For the moment, KISS.
|
|
*/
|
|
void
|
|
array_bitmap_copy(bits8 *destbitmap, int destoffset,
|
|
const bits8 *srcbitmap, int srcoffset,
|
|
int nitems)
|
|
{
|
|
int destbitmask,
|
|
destbitval,
|
|
srcbitmask,
|
|
srcbitval;
|
|
|
|
Assert(destbitmap);
|
|
if (nitems <= 0)
|
|
return; /* don't risk fetch off end of memory */
|
|
destbitmap += destoffset / 8;
|
|
destbitmask = 1 << (destoffset % 8);
|
|
destbitval = *destbitmap;
|
|
if (srcbitmap)
|
|
{
|
|
srcbitmap += srcoffset / 8;
|
|
srcbitmask = 1 << (srcoffset % 8);
|
|
srcbitval = *srcbitmap;
|
|
while (nitems-- > 0)
|
|
{
|
|
if (srcbitval & srcbitmask)
|
|
destbitval |= destbitmask;
|
|
else
|
|
destbitval &= ~destbitmask;
|
|
destbitmask <<= 1;
|
|
if (destbitmask == 0x100)
|
|
{
|
|
*destbitmap++ = destbitval;
|
|
destbitmask = 1;
|
|
if (nitems > 0)
|
|
destbitval = *destbitmap;
|
|
}
|
|
srcbitmask <<= 1;
|
|
if (srcbitmask == 0x100)
|
|
{
|
|
srcbitmap++;
|
|
srcbitmask = 1;
|
|
if (nitems > 0)
|
|
srcbitval = *srcbitmap;
|
|
}
|
|
}
|
|
if (destbitmask != 1)
|
|
*destbitmap = destbitval;
|
|
}
|
|
else
|
|
{
|
|
while (nitems-- > 0)
|
|
{
|
|
destbitval |= destbitmask;
|
|
destbitmask <<= 1;
|
|
if (destbitmask == 0x100)
|
|
{
|
|
*destbitmap++ = destbitval;
|
|
destbitmask = 1;
|
|
if (nitems > 0)
|
|
destbitval = *destbitmap;
|
|
}
|
|
}
|
|
if (destbitmask != 1)
|
|
*destbitmap = destbitval;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Compute space needed for a slice of an array
|
|
*
|
|
* We assume the caller has verified that the slice coordinates are valid.
|
|
*/
|
|
static int
|
|
array_slice_size(char *arraydataptr, bits8 *arraynullsptr,
|
|
int ndim, int *dim, int *lb,
|
|
int *st, int *endp,
|
|
int typlen, bool typbyval, char typalign)
|
|
{
|
|
int src_offset,
|
|
span[MAXDIM],
|
|
prod[MAXDIM],
|
|
dist[MAXDIM],
|
|
indx[MAXDIM];
|
|
char *ptr;
|
|
int i,
|
|
j,
|
|
inc;
|
|
int count = 0;
|
|
|
|
mda_get_range(ndim, span, st, endp);
|
|
|
|
/* Pretty easy for fixed element length without nulls ... */
|
|
if (typlen > 0 && !arraynullsptr)
|
|
return ArrayGetNItems(ndim, span) * att_align(typlen, typalign);
|
|
|
|
/* Else gotta do it the hard way */
|
|
src_offset = ArrayGetOffset(ndim, dim, lb, st);
|
|
ptr = array_seek(arraydataptr, 0, arraynullsptr, src_offset,
|
|
typlen, typbyval, typalign);
|
|
mda_get_prod(ndim, dim, prod);
|
|
mda_get_offset_values(ndim, dist, prod, span);
|
|
for (i = 0; i < ndim; i++)
|
|
indx[i] = 0;
|
|
j = ndim - 1;
|
|
do
|
|
{
|
|
if (dist[j])
|
|
{
|
|
ptr = array_seek(ptr, src_offset, arraynullsptr, dist[j],
|
|
typlen, typbyval, typalign);
|
|
src_offset += dist[j];
|
|
}
|
|
if (!array_get_isnull(arraynullsptr, src_offset))
|
|
{
|
|
inc = att_addlength(0, typlen, PointerGetDatum(ptr));
|
|
inc = att_align(inc, typalign);
|
|
ptr += inc;
|
|
count += inc;
|
|
}
|
|
src_offset++;
|
|
} while ((j = mda_next_tuple(ndim, indx, span)) != -1);
|
|
return count;
|
|
}
|
|
|
|
/*
|
|
* Extract a slice of an array into consecutive elements in the destination
|
|
* array.
|
|
*
|
|
* We assume the caller has verified that the slice coordinates are valid,
|
|
* allocated enough storage for the result, and initialized the header
|
|
* of the new array.
|
|
*/
|
|
static void
|
|
array_extract_slice(ArrayType *newarray,
|
|
int ndim,
|
|
int *dim,
|
|
int *lb,
|
|
char *arraydataptr,
|
|
bits8 *arraynullsptr,
|
|
int *st,
|
|
int *endp,
|
|
int typlen,
|
|
bool typbyval,
|
|
char typalign)
|
|
{
|
|
char *destdataptr = ARR_DATA_PTR(newarray);
|
|
bits8 *destnullsptr = ARR_NULLBITMAP(newarray);
|
|
char *srcdataptr;
|
|
int src_offset,
|
|
dest_offset,
|
|
prod[MAXDIM],
|
|
span[MAXDIM],
|
|
dist[MAXDIM],
|
|
indx[MAXDIM];
|
|
int i,
|
|
j,
|
|
inc;
|
|
|
|
src_offset = ArrayGetOffset(ndim, dim, lb, st);
|
|
srcdataptr = array_seek(arraydataptr, 0, arraynullsptr, src_offset,
|
|
typlen, typbyval, typalign);
|
|
mda_get_prod(ndim, dim, prod);
|
|
mda_get_range(ndim, span, st, endp);
|
|
mda_get_offset_values(ndim, dist, prod, span);
|
|
for (i = 0; i < ndim; i++)
|
|
indx[i] = 0;
|
|
dest_offset = 0;
|
|
j = ndim - 1;
|
|
do
|
|
{
|
|
if (dist[j])
|
|
{
|
|
/* skip unwanted elements */
|
|
srcdataptr = array_seek(srcdataptr, src_offset, arraynullsptr,
|
|
dist[j],
|
|
typlen, typbyval, typalign);
|
|
src_offset += dist[j];
|
|
}
|
|
inc = array_copy(destdataptr, 1,
|
|
srcdataptr, src_offset, arraynullsptr,
|
|
typlen, typbyval, typalign);
|
|
if (destnullsptr)
|
|
array_bitmap_copy(destnullsptr, dest_offset,
|
|
arraynullsptr, src_offset,
|
|
1);
|
|
destdataptr += inc;
|
|
srcdataptr += inc;
|
|
src_offset++;
|
|
dest_offset++;
|
|
} while ((j = mda_next_tuple(ndim, indx, span)) != -1);
|
|
}
|
|
|
|
/*
|
|
* Insert a slice into an array.
|
|
*
|
|
* ndim/dim[]/lb[] are dimensions of the original array. A new array with
|
|
* those same dimensions is to be constructed. destArray must already
|
|
* have been allocated and its header initialized.
|
|
*
|
|
* st[]/endp[] identify the slice to be replaced. Elements within the slice
|
|
* volume are taken from consecutive elements of the srcArray; elements
|
|
* outside it are copied from origArray.
|
|
*
|
|
* We assume the caller has verified that the slice coordinates are valid.
|
|
*/
|
|
static void
|
|
array_insert_slice(ArrayType *destArray,
|
|
ArrayType *origArray,
|
|
ArrayType *srcArray,
|
|
int ndim,
|
|
int *dim,
|
|
int *lb,
|
|
int *st,
|
|
int *endp,
|
|
int typlen,
|
|
bool typbyval,
|
|
char typalign)
|
|
{
|
|
char *destPtr = ARR_DATA_PTR(destArray);
|
|
char *origPtr = ARR_DATA_PTR(origArray);
|
|
char *srcPtr = ARR_DATA_PTR(srcArray);
|
|
bits8 *destBitmap = ARR_NULLBITMAP(destArray);
|
|
bits8 *origBitmap = ARR_NULLBITMAP(origArray);
|
|
bits8 *srcBitmap = ARR_NULLBITMAP(srcArray);
|
|
int orignitems = ArrayGetNItems(ARR_NDIM(origArray),
|
|
ARR_DIMS(origArray));
|
|
int dest_offset,
|
|
orig_offset,
|
|
src_offset,
|
|
prod[MAXDIM],
|
|
span[MAXDIM],
|
|
dist[MAXDIM],
|
|
indx[MAXDIM];
|
|
int i,
|
|
j,
|
|
inc;
|
|
|
|
dest_offset = ArrayGetOffset(ndim, dim, lb, st);
|
|
/* copy items before the slice start */
|
|
inc = array_copy(destPtr, dest_offset,
|
|
origPtr, 0, origBitmap,
|
|
typlen, typbyval, typalign);
|
|
destPtr += inc;
|
|
origPtr += inc;
|
|
if (destBitmap)
|
|
array_bitmap_copy(destBitmap, 0, origBitmap, 0, dest_offset);
|
|
orig_offset = dest_offset;
|
|
mda_get_prod(ndim, dim, prod);
|
|
mda_get_range(ndim, span, st, endp);
|
|
mda_get_offset_values(ndim, dist, prod, span);
|
|
for (i = 0; i < ndim; i++)
|
|
indx[i] = 0;
|
|
src_offset = 0;
|
|
j = ndim - 1;
|
|
do
|
|
{
|
|
/* Copy/advance over elements between here and next part of slice */
|
|
if (dist[j])
|
|
{
|
|
inc = array_copy(destPtr, dist[j],
|
|
origPtr, orig_offset, origBitmap,
|
|
typlen, typbyval, typalign);
|
|
destPtr += inc;
|
|
origPtr += inc;
|
|
if (destBitmap)
|
|
array_bitmap_copy(destBitmap, dest_offset,
|
|
origBitmap, orig_offset,
|
|
dist[j]);
|
|
dest_offset += dist[j];
|
|
orig_offset += dist[j];
|
|
}
|
|
/* Copy new element at this slice position */
|
|
inc = array_copy(destPtr, 1,
|
|
srcPtr, src_offset, srcBitmap,
|
|
typlen, typbyval, typalign);
|
|
if (destBitmap)
|
|
array_bitmap_copy(destBitmap, dest_offset,
|
|
srcBitmap, src_offset,
|
|
1);
|
|
destPtr += inc;
|
|
srcPtr += inc;
|
|
dest_offset++;
|
|
src_offset++;
|
|
/* Advance over old element at this slice position */
|
|
origPtr = array_seek(origPtr, orig_offset, origBitmap, 1,
|
|
typlen, typbyval, typalign);
|
|
orig_offset++;
|
|
} while ((j = mda_next_tuple(ndim, indx, span)) != -1);
|
|
|
|
/* don't miss any data at the end */
|
|
array_copy(destPtr, orignitems - orig_offset,
|
|
origPtr, orig_offset, origBitmap,
|
|
typlen, typbyval, typalign);
|
|
if (destBitmap)
|
|
array_bitmap_copy(destBitmap, dest_offset,
|
|
origBitmap, orig_offset,
|
|
orignitems - orig_offset);
|
|
}
|
|
|
|
/*
|
|
* array_type_coerce -- allow explicit or assignment coercion from
|
|
* one array type to another.
|
|
*
|
|
* array_type_length_coerce -- the same, for cases where both type and length
|
|
* coercion are done by a single function on the element type.
|
|
*
|
|
* Caller should have already verified that the source element type can be
|
|
* coerced into the target element type.
|
|
*/
|
|
Datum
|
|
array_type_coerce(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *src = PG_GETARG_ARRAYTYPE_P(0);
|
|
FmgrInfo *fmgr_info = fcinfo->flinfo;
|
|
|
|
return array_type_length_coerce_internal(src, -1, false, fmgr_info);
|
|
}
|
|
|
|
Datum
|
|
array_type_length_coerce(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *src = PG_GETARG_ARRAYTYPE_P(0);
|
|
int32 desttypmod = PG_GETARG_INT32(1);
|
|
bool isExplicit = PG_GETARG_BOOL(2);
|
|
FmgrInfo *fmgr_info = fcinfo->flinfo;
|
|
|
|
return array_type_length_coerce_internal(src, desttypmod,
|
|
isExplicit, fmgr_info);
|
|
}
|
|
|
|
static Datum
|
|
array_type_length_coerce_internal(ArrayType *src,
|
|
int32 desttypmod,
|
|
bool isExplicit,
|
|
FmgrInfo *fmgr_info)
|
|
{
|
|
Oid src_elem_type = ARR_ELEMTYPE(src);
|
|
typedef struct
|
|
{
|
|
Oid srctype;
|
|
Oid desttype;
|
|
FmgrInfo coerce_finfo;
|
|
ArrayMapState amstate;
|
|
} atc_extra;
|
|
atc_extra *my_extra;
|
|
FunctionCallInfoData locfcinfo;
|
|
|
|
/*
|
|
* We arrange to look up the coercion function only once per series of
|
|
* calls, assuming the input data type doesn't change underneath us.
|
|
* (Output type can't change.)
|
|
*/
|
|
my_extra = (atc_extra *) fmgr_info->fn_extra;
|
|
if (my_extra == NULL)
|
|
{
|
|
fmgr_info->fn_extra = MemoryContextAllocZero(fmgr_info->fn_mcxt,
|
|
sizeof(atc_extra));
|
|
my_extra = (atc_extra *) fmgr_info->fn_extra;
|
|
}
|
|
|
|
if (my_extra->srctype != src_elem_type)
|
|
{
|
|
Oid tgt_type = get_fn_expr_rettype(fmgr_info);
|
|
Oid tgt_elem_type;
|
|
Oid funcId;
|
|
|
|
if (tgt_type == InvalidOid)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("could not determine target array type")));
|
|
|
|
tgt_elem_type = get_element_type(tgt_type);
|
|
if (tgt_elem_type == InvalidOid)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("target type is not an array")));
|
|
|
|
/*
|
|
* We don't deal with domain constraints yet, so bail out. This isn't
|
|
* currently a problem, because we also don't support arrays of domain
|
|
* type elements either. But in the future we might. At that point
|
|
* consideration should be given to removing the check below and
|
|
* adding a domain constraints check to the coercion.
|
|
*/
|
|
if (getBaseType(tgt_elem_type) != tgt_elem_type)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("array coercion to domain type elements not "
|
|
"currently supported")));
|
|
|
|
if (!find_coercion_pathway(tgt_elem_type, src_elem_type,
|
|
COERCION_EXPLICIT, &funcId))
|
|
{
|
|
/* should never happen, but check anyway */
|
|
elog(ERROR, "no conversion function from %s to %s",
|
|
format_type_be(src_elem_type),
|
|
format_type_be(tgt_elem_type));
|
|
}
|
|
if (OidIsValid(funcId))
|
|
fmgr_info_cxt(funcId, &my_extra->coerce_finfo, fmgr_info->fn_mcxt);
|
|
else
|
|
my_extra->coerce_finfo.fn_oid = InvalidOid;
|
|
my_extra->srctype = src_elem_type;
|
|
my_extra->desttype = tgt_elem_type;
|
|
}
|
|
|
|
/*
|
|
* If it's binary-compatible, modify the element type in the array header,
|
|
* but otherwise leave the array as we received it.
|
|
*/
|
|
if (my_extra->coerce_finfo.fn_oid == InvalidOid)
|
|
{
|
|
ArrayType *result;
|
|
|
|
result = (ArrayType *) DatumGetPointer(datumCopy(PointerGetDatum(src),
|
|
false, -1));
|
|
ARR_ELEMTYPE(result) = my_extra->desttype;
|
|
PG_RETURN_ARRAYTYPE_P(result);
|
|
}
|
|
|
|
/*
|
|
* Use array_map to apply the function to each array element.
|
|
*
|
|
* We pass on the desttypmod and isExplicit flags whether or not the
|
|
* function wants them.
|
|
*/
|
|
InitFunctionCallInfoData(locfcinfo, &my_extra->coerce_finfo, 3,
|
|
NULL, NULL);
|
|
locfcinfo.arg[0] = PointerGetDatum(src);
|
|
locfcinfo.arg[1] = Int32GetDatum(desttypmod);
|
|
locfcinfo.arg[2] = BoolGetDatum(isExplicit);
|
|
locfcinfo.argnull[0] = false;
|
|
locfcinfo.argnull[1] = false;
|
|
locfcinfo.argnull[2] = false;
|
|
|
|
return array_map(&locfcinfo, my_extra->srctype, my_extra->desttype,
|
|
&my_extra->amstate);
|
|
}
|
|
|
|
/*
|
|
* array_length_coerce -- apply the element type's length-coercion routine
|
|
* to each element of the given array.
|
|
*/
|
|
Datum
|
|
array_length_coerce(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
|
|
int32 desttypmod = PG_GETARG_INT32(1);
|
|
bool isExplicit = PG_GETARG_BOOL(2);
|
|
FmgrInfo *fmgr_info = fcinfo->flinfo;
|
|
typedef struct
|
|
{
|
|
Oid elemtype;
|
|
FmgrInfo coerce_finfo;
|
|
ArrayMapState amstate;
|
|
} alc_extra;
|
|
alc_extra *my_extra;
|
|
FunctionCallInfoData locfcinfo;
|
|
|
|
/* If no typmod is provided, shortcircuit the whole thing */
|
|
if (desttypmod < 0)
|
|
PG_RETURN_ARRAYTYPE_P(v);
|
|
|
|
/*
|
|
* We arrange to look up the element type's coercion function only once
|
|
* per series of calls, assuming the element type doesn't change
|
|
* underneath us.
|
|
*/
|
|
my_extra = (alc_extra *) fmgr_info->fn_extra;
|
|
if (my_extra == NULL)
|
|
{
|
|
fmgr_info->fn_extra = MemoryContextAllocZero(fmgr_info->fn_mcxt,
|
|
sizeof(alc_extra));
|
|
my_extra = (alc_extra *) fmgr_info->fn_extra;
|
|
}
|
|
|
|
if (my_extra->elemtype != ARR_ELEMTYPE(v))
|
|
{
|
|
Oid funcId;
|
|
|
|
funcId = find_typmod_coercion_function(ARR_ELEMTYPE(v));
|
|
|
|
if (OidIsValid(funcId))
|
|
fmgr_info_cxt(funcId, &my_extra->coerce_finfo, fmgr_info->fn_mcxt);
|
|
else
|
|
my_extra->coerce_finfo.fn_oid = InvalidOid;
|
|
my_extra->elemtype = ARR_ELEMTYPE(v);
|
|
}
|
|
|
|
/*
|
|
* If we didn't find a coercion function, return the array unmodified
|
|
* (this should not happen in the normal course of things, but might
|
|
* happen if this function is called manually).
|
|
*/
|
|
if (my_extra->coerce_finfo.fn_oid == InvalidOid)
|
|
PG_RETURN_ARRAYTYPE_P(v);
|
|
|
|
/*
|
|
* Use array_map to apply the function to each array element.
|
|
*
|
|
* Note: we pass isExplicit whether or not the function wants it ...
|
|
*/
|
|
InitFunctionCallInfoData(locfcinfo, &my_extra->coerce_finfo, 3,
|
|
NULL, NULL);
|
|
locfcinfo.arg[0] = PointerGetDatum(v);
|
|
locfcinfo.arg[1] = Int32GetDatum(desttypmod);
|
|
locfcinfo.arg[2] = BoolGetDatum(isExplicit);
|
|
locfcinfo.argnull[0] = false;
|
|
locfcinfo.argnull[1] = false;
|
|
locfcinfo.argnull[2] = false;
|
|
|
|
return array_map(&locfcinfo, ARR_ELEMTYPE(v), ARR_ELEMTYPE(v),
|
|
&my_extra->amstate);
|
|
}
|
|
|
|
/*
|
|
* accumArrayResult - accumulate one (more) Datum for an array result
|
|
*
|
|
* astate is working state (NULL on first call)
|
|
* rcontext is where to keep working state
|
|
*/
|
|
ArrayBuildState *
|
|
accumArrayResult(ArrayBuildState *astate,
|
|
Datum dvalue, bool disnull,
|
|
Oid element_type,
|
|
MemoryContext rcontext)
|
|
{
|
|
MemoryContext arr_context,
|
|
oldcontext;
|
|
|
|
if (astate == NULL)
|
|
{
|
|
/* First time through --- initialize */
|
|
|
|
/* Make a temporary context to hold all the junk */
|
|
arr_context = AllocSetContextCreate(rcontext,
|
|
"accumArrayResult",
|
|
ALLOCSET_DEFAULT_MINSIZE,
|
|
ALLOCSET_DEFAULT_INITSIZE,
|
|
ALLOCSET_DEFAULT_MAXSIZE);
|
|
oldcontext = MemoryContextSwitchTo(arr_context);
|
|
astate = (ArrayBuildState *) palloc(sizeof(ArrayBuildState));
|
|
astate->mcontext = arr_context;
|
|
astate->dvalues = (Datum *)
|
|
palloc(ARRAY_ELEMS_CHUNKSIZE * sizeof(Datum));
|
|
astate->dnulls = (bool *)
|
|
palloc(ARRAY_ELEMS_CHUNKSIZE * sizeof(bool));
|
|
astate->nelems = 0;
|
|
astate->element_type = element_type;
|
|
get_typlenbyvalalign(element_type,
|
|
&astate->typlen,
|
|
&astate->typbyval,
|
|
&astate->typalign);
|
|
}
|
|
else
|
|
{
|
|
oldcontext = MemoryContextSwitchTo(astate->mcontext);
|
|
Assert(astate->element_type == element_type);
|
|
/* enlarge dvalues[]/dnulls[] if needed */
|
|
if ((astate->nelems % ARRAY_ELEMS_CHUNKSIZE) == 0)
|
|
{
|
|
astate->dvalues = (Datum *)
|
|
repalloc(astate->dvalues,
|
|
(astate->nelems + ARRAY_ELEMS_CHUNKSIZE) * sizeof(Datum));
|
|
astate->dnulls = (bool *)
|
|
repalloc(astate->dnulls,
|
|
(astate->nelems + ARRAY_ELEMS_CHUNKSIZE) * sizeof(bool));
|
|
}
|
|
}
|
|
|
|
/* Use datumCopy to ensure pass-by-ref stuff is copied into mcontext */
|
|
if (!disnull && !astate->typbyval)
|
|
dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
|
|
|
|
astate->dvalues[astate->nelems] = dvalue;
|
|
astate->dnulls[astate->nelems] = disnull;
|
|
astate->nelems++;
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
return astate;
|
|
}
|
|
|
|
/*
|
|
* makeArrayResult - produce 1-D final result of accumArrayResult
|
|
*
|
|
* astate is working state (not NULL)
|
|
* rcontext is where to construct result
|
|
*/
|
|
Datum
|
|
makeArrayResult(ArrayBuildState *astate,
|
|
MemoryContext rcontext)
|
|
{
|
|
int dims[1];
|
|
int lbs[1];
|
|
|
|
dims[0] = astate->nelems;
|
|
lbs[0] = 1;
|
|
|
|
return makeMdArrayResult(astate, 1, dims, lbs, rcontext);
|
|
}
|
|
|
|
/*
|
|
* makeMdArrayResult - produce multi-D final result of accumArrayResult
|
|
*
|
|
* beware: no check that specified dimensions match the number of values
|
|
* accumulated.
|
|
*
|
|
* astate is working state (not NULL)
|
|
* rcontext is where to construct result
|
|
*/
|
|
Datum
|
|
makeMdArrayResult(ArrayBuildState *astate,
|
|
int ndims,
|
|
int *dims,
|
|
int *lbs,
|
|
MemoryContext rcontext)
|
|
{
|
|
ArrayType *result;
|
|
MemoryContext oldcontext;
|
|
|
|
/* Build the final array result in rcontext */
|
|
oldcontext = MemoryContextSwitchTo(rcontext);
|
|
|
|
result = construct_md_array(astate->dvalues,
|
|
astate->dnulls,
|
|
ndims,
|
|
dims,
|
|
lbs,
|
|
astate->element_type,
|
|
astate->typlen,
|
|
astate->typbyval,
|
|
astate->typalign);
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
/* Clean up all the junk */
|
|
MemoryContextDelete(astate->mcontext);
|
|
|
|
return PointerGetDatum(result);
|
|
}
|
|
|
|
Datum
|
|
array_larger(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *v1,
|
|
*v2,
|
|
*result;
|
|
|
|
v1 = PG_GETARG_ARRAYTYPE_P(0);
|
|
v2 = PG_GETARG_ARRAYTYPE_P(1);
|
|
|
|
result = ((array_cmp(fcinfo) > 0) ? v1 : v2);
|
|
|
|
PG_RETURN_ARRAYTYPE_P(result);
|
|
}
|
|
|
|
Datum
|
|
array_smaller(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *v1,
|
|
*v2,
|
|
*result;
|
|
|
|
v1 = PG_GETARG_ARRAYTYPE_P(0);
|
|
v2 = PG_GETARG_ARRAYTYPE_P(1);
|
|
|
|
result = ((array_cmp(fcinfo) < 0) ? v1 : v2);
|
|
|
|
PG_RETURN_ARRAYTYPE_P(result);
|
|
}
|