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