aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2014-11-25 12:21:22 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2014-11-25 12:21:28 -0500
commitbac27394a1c69c20ec904729c593e59485c75c69 (patch)
tree7bdf15b078bfcef745a5bb2c7c479d6f8bd45f15 /src/backend/utils
parent25976710dfd8611d3fc79c0c1e20179ff7a940ec (diff)
downloadpostgresql-bac27394a1c69c20ec904729c593e59485c75c69.tar.gz
postgresql-bac27394a1c69c20ec904729c593e59485c75c69.zip
Support arrays as input to array_agg() and ARRAY(SELECT ...).
These cases formerly failed with errors about "could not find array type for data type". Now they yield arrays of the same element type and one higher dimension. The implementation involves creating functions with API similar to the existing accumArrayResult() family. I (tgl) also extended the base family by adding an initArrayResult() function, which allows callers to avoid special-casing the zero-inputs case if they just want an empty array as result. (Not all do, so the previous calling convention remains valid.) This allowed simplifying some existing code in xml.c and plperl.c. Ali Akbar, reviewed by Pavel Stehule, significantly modified by me
Diffstat (limited to 'src/backend/utils')
-rw-r--r--src/backend/utils/adt/array_userfuncs.c88
-rw-r--r--src/backend/utils/adt/arrayfuncs.c495
-rw-r--r--src/backend/utils/adt/xml.c31
-rw-r--r--src/backend/utils/cache/lsyscache.c21
4 files changed, 570 insertions, 65 deletions
diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c
index 831466dec91..50ea4d226b7 100644
--- a/src/backend/utils/adt/array_userfuncs.c
+++ b/src/backend/utils/adt/array_userfuncs.c
@@ -471,7 +471,7 @@ create_singleton_array(FunctionCallInfo fcinfo,
/*
- * ARRAY_AGG aggregate function
+ * ARRAY_AGG(anynonarray) aggregate function
*/
Datum
array_agg_transfn(PG_FUNCTION_ARGS)
@@ -486,6 +486,12 @@ array_agg_transfn(PG_FUNCTION_ARGS)
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not determine input data type")));
+ /*
+ * Note: we do not need a run-time check about whether arg1_typeid is a
+ * valid array element type, because the parser would have verified that
+ * while resolving the input/result types of this polymorphic aggregate.
+ */
+
if (!AggCheckCallContext(fcinfo, &aggcontext))
{
/* cannot be called directly because of internal-type argument */
@@ -516,18 +522,13 @@ array_agg_finalfn(PG_FUNCTION_ARGS)
int dims[1];
int lbs[1];
- /*
- * Test for null before Asserting we are in right context. This is to
- * avoid possible Assert failure in 8.4beta installations, where it is
- * possible for users to create NULL constants of type internal.
- */
- if (PG_ARGISNULL(0))
- PG_RETURN_NULL(); /* returns null iff no input values */
-
/* cannot be called directly because of internal-type argument */
Assert(AggCheckCallContext(fcinfo, NULL));
- state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+ state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+ if (state == NULL)
+ PG_RETURN_NULL(); /* returns null iff no input values */
dims[0] = state->nelems;
lbs[0] = 1;
@@ -544,3 +545,70 @@ array_agg_finalfn(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(result);
}
+
+/*
+ * ARRAY_AGG(anyarray) aggregate function
+ */
+Datum
+array_agg_array_transfn(PG_FUNCTION_ARGS)
+{
+ Oid arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ MemoryContext aggcontext;
+ ArrayBuildStateArr *state;
+
+ if (arg1_typeid == InvalidOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not determine input data type")));
+
+ /*
+ * Note: we do not need a run-time check about whether arg1_typeid is a
+ * valid array type, because the parser would have verified that while
+ * resolving the input/result types of this polymorphic aggregate.
+ */
+
+ if (!AggCheckCallContext(fcinfo, &aggcontext))
+ {
+ /* cannot be called directly because of internal-type argument */
+ elog(ERROR, "array_agg_array_transfn called in non-aggregate context");
+ }
+
+ state = PG_ARGISNULL(0) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
+ state = accumArrayResultArr(state,
+ PG_GETARG_DATUM(1),
+ PG_ARGISNULL(1),
+ arg1_typeid,
+ aggcontext);
+
+ /*
+ * The transition type for array_agg() is declared to be "internal", which
+ * is a pass-by-value type the same size as a pointer. So we can safely
+ * pass the ArrayBuildStateArr pointer through nodeAgg.c's machinations.
+ */
+ PG_RETURN_POINTER(state);
+}
+
+Datum
+array_agg_array_finalfn(PG_FUNCTION_ARGS)
+{
+ Datum result;
+ ArrayBuildStateArr *state;
+
+ /* cannot be called directly because of internal-type argument */
+ Assert(AggCheckCallContext(fcinfo, NULL));
+
+ state = PG_ARGISNULL(0) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
+
+ if (state == NULL)
+ PG_RETURN_NULL(); /* returns null iff no input values */
+
+ /*
+ * Make the result. We cannot release the ArrayBuildStateArr because
+ * sometimes aggregate final functions are re-executed. Rather, it is
+ * nodeAgg.c's responsibility to reset the aggcontext when it's safe to do
+ * so.
+ */
+ result = makeArrayResultArr(state, CurrentMemoryContext, false);
+
+ PG_RETURN_DATUM(result);
+}
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 6c8b41d2a91..743351b95e0 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4574,9 +4574,57 @@ array_insert_slice(ArrayType *destArray,
}
/*
+ * 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
+ *
+ * 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.
+ */
+ArrayBuildState *
+initArrayResult(Oid element_type, MemoryContext rcontext)
+{
+ ArrayBuildState *astate;
+ MemoryContext arr_context;
+
+ /* Make a temporary context to hold all the junk */
+ 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->alen = 64; /* 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 (NULL on first call)
+ * 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 *
@@ -4585,45 +4633,28 @@ accumArrayResult(ArrayBuildState *astate,
Oid element_type,
MemoryContext rcontext)
{
- MemoryContext arr_context,
- oldcontext;
+ MemoryContext oldcontext;
if (astate == NULL)
{
/* First time through --- initialize */
-
- /* Make a temporary context to hold all the junk */
- arr_context = AllocSetContextCreate(rcontext,
- "accumArrayResult",
- ALLOCSET_DEFAULT_MINSIZE,
- ALLOCSET_DEFAULT_INITSIZE,
- ALLOCSET_DEFAULT_MAXSIZE);
- oldcontext = MemoryContextSwitchTo(arr_context);
- astate = (ArrayBuildState *) palloc(sizeof(ArrayBuildState));
- astate->mcontext = arr_context;
- astate->alen = 64; /* arbitrary starting array size */
- astate->dvalues = (Datum *) palloc(astate->alen * sizeof(Datum));
- astate->dnulls = (bool *) palloc(astate->alen * sizeof(bool));
- astate->nelems = 0;
- astate->element_type = element_type;
- get_typlenbyvalalign(element_type,
- &astate->typlen,
- &astate->typbyval,
- &astate->typalign);
+ astate = initArrayResult(element_type, rcontext);
}
else
{
- oldcontext = MemoryContextSwitchTo(astate->mcontext);
Assert(astate->element_type == element_type);
- /* 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));
- }
+ }
+
+ 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));
}
/*
@@ -4654,20 +4685,23 @@ accumArrayResult(ArrayBuildState *astate,
/*
* makeArrayResult - produce 1-D final result of accumArrayResult
*
- * astate is working state (not NULL)
+ * 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, 1, dims, lbs, rcontext, true);
+ return makeMdArrayResult(astate, ndims, dims, lbs, rcontext, true);
}
/*
@@ -4676,7 +4710,7 @@ makeArrayResult(ArrayBuildState *astate,
* beware: no check that specified dimensions match the number of values
* accumulated.
*
- * astate is working state (not NULL)
+ * astate is working state (must not be NULL)
* rcontext is where to construct result
* release is true if okay to release working state
*/
@@ -4713,6 +4747,397 @@ makeMdArrayResult(ArrayBuildState *astate,
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
+ * rcontext is where to keep working state
+ */
+ArrayBuildStateArr *
+initArrayResultArr(Oid array_type, Oid element_type, MemoryContext rcontext)
+{
+ ArrayBuildStateArr *astate;
+ MemoryContext arr_context;
+
+ /* Make a temporary context to hold all the junk */
+ 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;
+
+ /* 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)
+ {
+ /* First time through --- initialize */
+ Oid 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))));
+ astate = initArrayResultArr(array_type, element_type, rcontext);
+ }
+ 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)
+ 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
+ */
+ArrayBuildStateAny *
+initArrayResultAny(Oid input_type, MemoryContext rcontext)
+{
+ ArrayBuildStateAny *astate;
+ Oid element_type = get_element_type(input_type);
+
+ if (OidIsValid(element_type))
+ {
+ /* Array case */
+ ArrayBuildStateArr *arraystate;
+
+ arraystate = initArrayResultArr(input_type, element_type, rcontext);
+ 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);
+ 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);
+
+ 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)
{
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 119dfc7efec..7d903120e5b 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -143,7 +143,7 @@ static xmlDocPtr xml_parse(text *data, XmlOptionType xmloption_arg,
bool preserve_whitespace, int encoding);
static text *xml_xmlnodetoxmltype(xmlNodePtr cur);
static int xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
- ArrayBuildState **astate);
+ ArrayBuildState *astate);
#endif /* USE_LIBXML */
static StringInfo query_to_xml_internal(const char *query, char *tablename,
@@ -3648,7 +3648,7 @@ xml_xmlnodetoxmltype(xmlNodePtr cur)
/*
* Convert an XML XPath object (the result of evaluating an XPath expression)
- * to an array of xml values, which is returned at *astate. The function
+ * to an array of xml values, which are appended to astate. The function
* result value is the number of elements in the array.
*
* If "astate" is NULL then we don't generate the array value, but we still
@@ -3660,16 +3660,13 @@ xml_xmlnodetoxmltype(xmlNodePtr cur)
*/
static int
xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
- ArrayBuildState **astate)
+ ArrayBuildState *astate)
{
int result = 0;
Datum datum;
Oid datumtype;
char *result_str;
- if (astate != NULL)
- *astate = NULL;
-
switch (xpathobj->type)
{
case XPATH_NODESET:
@@ -3683,9 +3680,8 @@ xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
for (i = 0; i < result; i++)
{
datum = PointerGetDatum(xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i]));
- *astate = accumArrayResult(*astate, datum,
- false, XMLOID,
- CurrentMemoryContext);
+ (void) accumArrayResult(astate, datum, false,
+ XMLOID, CurrentMemoryContext);
}
}
}
@@ -3721,9 +3717,8 @@ xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
/* Common code for scalar-value cases */
result_str = map_sql_value_to_xml_value(datum, datumtype, true);
datum = PointerGetDatum(cstring_to_xmltype(result_str));
- *astate = accumArrayResult(*astate, datum,
- false, XMLOID,
- CurrentMemoryContext);
+ (void) accumArrayResult(astate, datum, false,
+ XMLOID, CurrentMemoryContext);
return 1;
}
@@ -3741,7 +3736,7 @@ xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
*/
static void
xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
- int *res_nitems, ArrayBuildState **astate)
+ int *res_nitems, ArrayBuildState *astate)
{
PgXmlErrorContext *xmlerrcxt;
volatile xmlParserCtxtPtr ctxt = NULL;
@@ -3933,16 +3928,12 @@ xpath(PG_FUNCTION_ARGS)
text *xpath_expr_text = PG_GETARG_TEXT_P(0);
xmltype *data = PG_GETARG_XML_P(1);
ArrayType *namespaces = PG_GETARG_ARRAYTYPE_P(2);
- int res_nitems;
ArrayBuildState *astate;
+ astate = initArrayResult(XMLOID, CurrentMemoryContext);
xpath_internal(xpath_expr_text, data, namespaces,
- &res_nitems, &astate);
-
- if (res_nitems == 0)
- PG_RETURN_ARRAYTYPE_P(construct_empty_array(XMLOID));
- else
- PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate, CurrentMemoryContext));
+ NULL, astate);
+ PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate, CurrentMemoryContext));
#else
NO_XML_SUPPORT();
return 0;
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 552e498cf57..73138e0a5bb 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2355,6 +2355,27 @@ get_array_type(Oid typid)
}
/*
+ * get_promoted_array_type
+ *
+ * The "promoted" type is what you'd get from an ARRAY(SELECT ...)
+ * construct, that is, either the corresponding "true" array type
+ * if the input is a scalar type that has such an array type,
+ * or the same type if the input is already a "true" array type.
+ * Returns InvalidOid if neither rule is satisfied.
+ */
+Oid
+get_promoted_array_type(Oid typid)
+{
+ Oid array_type = get_array_type(typid);
+
+ if (OidIsValid(array_type))
+ return array_type;
+ if (OidIsValid(get_element_type(typid)))
+ return typid;
+ return InvalidOid;
+}
+
+/*
* get_base_element_type
* Given the type OID, get the typelem, looking "through" any domain
* to its underlying array type.