mirror of
https://github.com/postgres/postgres.git
synced 2025-06-30 21:42:05 +03:00
Allow parallel aggregate on string_agg and array_agg
This adds combine, serial and deserial functions for the array_agg() and string_agg() aggregate functions, thus allowing these aggregates to partake in partial aggregations. This allows both parallel aggregation to take place when these aggregates are present and also allows additional partition-wise aggregation plan shapes to include plans that require additional aggregation once the partially aggregated results from the partitions have been combined. Author: David Rowley Reviewed-by: Andres Freund, Tomas Vondra, Stephen Frost, Tom Lane Discussion: https://postgr.es/m/CAKJS1f9sx_6GTcvd6TMuZnNtCh0VhBzhX6FZqw17TgVFH-ga_A@mail.gmail.com
This commit is contained in:
@ -13,12 +13,33 @@
|
||||
#include "postgres.h"
|
||||
|
||||
#include "catalog/pg_type.h"
|
||||
#include "libpq/pqformat.h"
|
||||
#include "common/int.h"
|
||||
#include "port/pg_bitutils.h"
|
||||
#include "utils/array.h"
|
||||
#include "utils/datum.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/typcache.h"
|
||||
|
||||
/*
|
||||
* SerialIOData
|
||||
* Used for caching element-type data in array_agg_serialize
|
||||
*/
|
||||
typedef struct SerialIOData
|
||||
{
|
||||
FmgrInfo typsend;
|
||||
} SerialIOData;
|
||||
|
||||
/*
|
||||
* DeserialIOData
|
||||
* Used for caching element-type data in array_agg_deserialize
|
||||
*/
|
||||
typedef struct DeserialIOData
|
||||
{
|
||||
FmgrInfo typreceive;
|
||||
Oid typioparam;
|
||||
} DeserialIOData;
|
||||
|
||||
static Datum array_position_common(FunctionCallInfo fcinfo);
|
||||
|
||||
@ -499,6 +520,316 @@ array_agg_transfn(PG_FUNCTION_ARGS)
|
||||
PG_RETURN_POINTER(state);
|
||||
}
|
||||
|
||||
Datum
|
||||
array_agg_combine(PG_FUNCTION_ARGS)
|
||||
{
|
||||
ArrayBuildState *state1;
|
||||
ArrayBuildState *state2;
|
||||
MemoryContext agg_context;
|
||||
MemoryContext old_context;
|
||||
|
||||
if (!AggCheckCallContext(fcinfo, &agg_context))
|
||||
elog(ERROR, "aggregate function called in non-aggregate context");
|
||||
|
||||
state1 = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
|
||||
state2 = PG_ARGISNULL(1) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(1);
|
||||
|
||||
if (state2 == NULL)
|
||||
{
|
||||
/*
|
||||
* NULL state2 is easy, just return state1, which we know is already
|
||||
* in the agg_context
|
||||
*/
|
||||
if (state1 == NULL)
|
||||
PG_RETURN_NULL();
|
||||
PG_RETURN_POINTER(state1);
|
||||
}
|
||||
|
||||
if (state1 == NULL)
|
||||
{
|
||||
/* We must copy state2's data into the agg_context */
|
||||
state1 = initArrayResultWithSize(state2->element_type, agg_context,
|
||||
false, state2->alen);
|
||||
|
||||
old_context = MemoryContextSwitchTo(agg_context);
|
||||
|
||||
for (int i = 0; i < state2->nelems; i++)
|
||||
{
|
||||
if (!state2->dnulls[i])
|
||||
state1->dvalues[i] = datumCopy(state2->dvalues[i],
|
||||
state1->typbyval,
|
||||
state1->typlen);
|
||||
else
|
||||
state1->dvalues[i] = (Datum) 0;
|
||||
}
|
||||
|
||||
MemoryContextSwitchTo(old_context);
|
||||
|
||||
memcpy(state1->dnulls, state2->dnulls, sizeof(bool) * state2->nelems);
|
||||
|
||||
state1->nelems = state2->nelems;
|
||||
|
||||
PG_RETURN_POINTER(state1);
|
||||
}
|
||||
else if (state2->nelems > 0)
|
||||
{
|
||||
/* We only need to combine the two states if state2 has any elements */
|
||||
int reqsize = state1->nelems + state2->nelems;
|
||||
MemoryContext oldContext = MemoryContextSwitchTo(state1->mcontext);
|
||||
|
||||
Assert(state1->element_type == state2->element_type);
|
||||
|
||||
/* Enlarge state1 arrays if needed */
|
||||
if (state1->alen < reqsize)
|
||||
{
|
||||
/* Use a power of 2 size rather than allocating just reqsize */
|
||||
state1->alen = pg_nextpower2_32(reqsize);
|
||||
state1->dvalues = (Datum *) repalloc(state1->dvalues,
|
||||
state1->alen * sizeof(Datum));
|
||||
state1->dnulls = (bool *) repalloc(state1->dnulls,
|
||||
state1->alen * sizeof(bool));
|
||||
}
|
||||
|
||||
/* Copy in the state2 elements to the end of the state1 arrays */
|
||||
for (int i = 0; i < state2->nelems; i++)
|
||||
{
|
||||
if (!state2->dnulls[i])
|
||||
state1->dvalues[i + state1->nelems] =
|
||||
datumCopy(state2->dvalues[i],
|
||||
state1->typbyval,
|
||||
state1->typlen);
|
||||
else
|
||||
state1->dvalues[i + state1->nelems] = (Datum) 0;
|
||||
}
|
||||
|
||||
memcpy(&state1->dnulls[state1->nelems], state2->dnulls,
|
||||
sizeof(bool) * state2->nelems);
|
||||
|
||||
state1->nelems = reqsize;
|
||||
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
}
|
||||
|
||||
PG_RETURN_POINTER(state1);
|
||||
}
|
||||
|
||||
/*
|
||||
* array_agg_serialize
|
||||
* Serialize ArrayBuildState into bytea.
|
||||
*/
|
||||
Datum
|
||||
array_agg_serialize(PG_FUNCTION_ARGS)
|
||||
{
|
||||
ArrayBuildState *state;
|
||||
StringInfoData buf;
|
||||
bytea *result;
|
||||
|
||||
/* cannot be called directly because of internal-type argument */
|
||||
Assert(AggCheckCallContext(fcinfo, NULL));
|
||||
|
||||
state = (ArrayBuildState *) PG_GETARG_POINTER(0);
|
||||
|
||||
pq_begintypsend(&buf);
|
||||
|
||||
/*
|
||||
* element_type. Putting this first is more convenient in deserialization
|
||||
*/
|
||||
pq_sendint32(&buf, state->element_type);
|
||||
|
||||
/*
|
||||
* nelems -- send first so we know how large to make the dvalues and
|
||||
* dnulls array during deserialization.
|
||||
*/
|
||||
pq_sendint64(&buf, state->nelems);
|
||||
|
||||
/* alen can be decided during deserialization */
|
||||
|
||||
/* typlen */
|
||||
pq_sendint16(&buf, state->typlen);
|
||||
|
||||
/* typbyval */
|
||||
pq_sendbyte(&buf, state->typbyval);
|
||||
|
||||
/* typalign */
|
||||
pq_sendbyte(&buf, state->typalign);
|
||||
|
||||
/* dnulls */
|
||||
pq_sendbytes(&buf, (char *) state->dnulls, sizeof(bool) * state->nelems);
|
||||
|
||||
/*
|
||||
* dvalues. By agreement with array_agg_deserialize, when the element
|
||||
* type is byval, we just transmit the Datum array as-is, including any
|
||||
* null elements. For by-ref types, we must invoke the element type's
|
||||
* send function, and we skip null elements (which is why the nulls flags
|
||||
* must be sent first).
|
||||
*/
|
||||
if (state->typbyval)
|
||||
pq_sendbytes(&buf, (char *) state->dvalues,
|
||||
sizeof(Datum) * state->nelems);
|
||||
else
|
||||
{
|
||||
SerialIOData *iodata;
|
||||
int i;
|
||||
|
||||
/* Avoid repeat catalog lookups for typsend function */
|
||||
iodata = (SerialIOData *) fcinfo->flinfo->fn_extra;
|
||||
if (iodata == NULL)
|
||||
{
|
||||
Oid typsend;
|
||||
bool typisvarlena;
|
||||
|
||||
iodata = (SerialIOData *)
|
||||
MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
|
||||
sizeof(SerialIOData));
|
||||
getTypeBinaryOutputInfo(state->element_type, &typsend,
|
||||
&typisvarlena);
|
||||
fmgr_info_cxt(typsend, &iodata->typsend,
|
||||
fcinfo->flinfo->fn_mcxt);
|
||||
fcinfo->flinfo->fn_extra = (void *) iodata;
|
||||
}
|
||||
|
||||
for (i = 0; i < state->nelems; i++)
|
||||
{
|
||||
bytea *outputbytes;
|
||||
|
||||
if (state->dnulls[i])
|
||||
continue;
|
||||
outputbytes = SendFunctionCall(&iodata->typsend,
|
||||
state->dvalues[i]);
|
||||
pq_sendint32(&buf, VARSIZE(outputbytes) - VARHDRSZ);
|
||||
pq_sendbytes(&buf, VARDATA(outputbytes),
|
||||
VARSIZE(outputbytes) - VARHDRSZ);
|
||||
}
|
||||
}
|
||||
|
||||
result = pq_endtypsend(&buf);
|
||||
|
||||
PG_RETURN_BYTEA_P(result);
|
||||
}
|
||||
|
||||
Datum
|
||||
array_agg_deserialize(PG_FUNCTION_ARGS)
|
||||
{
|
||||
bytea *sstate;
|
||||
ArrayBuildState *result;
|
||||
StringInfoData buf;
|
||||
Oid element_type;
|
||||
int64 nelems;
|
||||
const char *temp;
|
||||
|
||||
if (!AggCheckCallContext(fcinfo, NULL))
|
||||
elog(ERROR, "aggregate function called in non-aggregate context");
|
||||
|
||||
sstate = PG_GETARG_BYTEA_PP(0);
|
||||
|
||||
/*
|
||||
* Copy the bytea into a StringInfo so that we can "receive" it using the
|
||||
* standard recv-function infrastructure.
|
||||
*/
|
||||
initStringInfo(&buf);
|
||||
appendBinaryStringInfo(&buf,
|
||||
VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate));
|
||||
|
||||
/* element_type */
|
||||
element_type = pq_getmsgint(&buf, 4);
|
||||
|
||||
/* nelems */
|
||||
nelems = pq_getmsgint64(&buf);
|
||||
|
||||
/* Create output ArrayBuildState with the needed number of elements */
|
||||
result = initArrayResultWithSize(element_type, CurrentMemoryContext,
|
||||
false, nelems);
|
||||
result->nelems = nelems;
|
||||
|
||||
/* typlen */
|
||||
result->typlen = pq_getmsgint(&buf, 2);
|
||||
|
||||
/* typbyval */
|
||||
result->typbyval = pq_getmsgbyte(&buf);
|
||||
|
||||
/* typalign */
|
||||
result->typalign = pq_getmsgbyte(&buf);
|
||||
|
||||
/* dnulls */
|
||||
temp = pq_getmsgbytes(&buf, sizeof(bool) * nelems);
|
||||
memcpy(result->dnulls, temp, sizeof(bool) * nelems);
|
||||
|
||||
/* dvalues --- see comment in array_agg_serialize */
|
||||
if (result->typbyval)
|
||||
{
|
||||
temp = pq_getmsgbytes(&buf, sizeof(Datum) * nelems);
|
||||
memcpy(result->dvalues, temp, sizeof(Datum) * nelems);
|
||||
}
|
||||
else
|
||||
{
|
||||
DeserialIOData *iodata;
|
||||
|
||||
/* Avoid repeat catalog lookups for typreceive function */
|
||||
iodata = (DeserialIOData *) fcinfo->flinfo->fn_extra;
|
||||
if (iodata == NULL)
|
||||
{
|
||||
Oid typreceive;
|
||||
|
||||
iodata = (DeserialIOData *)
|
||||
MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
|
||||
sizeof(DeserialIOData));
|
||||
getTypeBinaryInputInfo(element_type, &typreceive,
|
||||
&iodata->typioparam);
|
||||
fmgr_info_cxt(typreceive, &iodata->typreceive,
|
||||
fcinfo->flinfo->fn_mcxt);
|
||||
fcinfo->flinfo->fn_extra = (void *) iodata;
|
||||
}
|
||||
|
||||
for (int i = 0; i < nelems; i++)
|
||||
{
|
||||
int itemlen;
|
||||
StringInfoData elem_buf;
|
||||
char csave;
|
||||
|
||||
if (result->dnulls[i])
|
||||
{
|
||||
result->dvalues[i] = (Datum) 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
itemlen = pq_getmsgint(&buf, 4);
|
||||
if (itemlen < 0 || itemlen > (buf.len - buf.cursor))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
|
||||
errmsg("insufficient data left in message")));
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
result->dvalues[i] = ReceiveFunctionCall(&iodata->typreceive,
|
||||
&elem_buf,
|
||||
iodata->typioparam,
|
||||
-1);
|
||||
|
||||
buf.data[buf.cursor] = csave;
|
||||
}
|
||||
}
|
||||
|
||||
pq_getmsgend(&buf);
|
||||
pfree(buf.data);
|
||||
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
|
||||
Datum
|
||||
array_agg_finalfn(PG_FUNCTION_ARGS)
|
||||
{
|
||||
@ -578,6 +909,299 @@ array_agg_array_transfn(PG_FUNCTION_ARGS)
|
||||
PG_RETURN_POINTER(state);
|
||||
}
|
||||
|
||||
Datum
|
||||
array_agg_array_combine(PG_FUNCTION_ARGS)
|
||||
{
|
||||
ArrayBuildStateArr *state1;
|
||||
ArrayBuildStateArr *state2;
|
||||
MemoryContext agg_context;
|
||||
MemoryContext old_context;
|
||||
|
||||
if (!AggCheckCallContext(fcinfo, &agg_context))
|
||||
elog(ERROR, "aggregate function called in non-aggregate context");
|
||||
|
||||
state1 = PG_ARGISNULL(0) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
|
||||
state2 = PG_ARGISNULL(1) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(1);
|
||||
|
||||
if (state2 == NULL)
|
||||
{
|
||||
/*
|
||||
* NULL state2 is easy, just return state1, which we know is already
|
||||
* in the agg_context
|
||||
*/
|
||||
if (state1 == NULL)
|
||||
PG_RETURN_NULL();
|
||||
PG_RETURN_POINTER(state1);
|
||||
}
|
||||
|
||||
if (state1 == NULL)
|
||||
{
|
||||
/* We must copy state2's data into the agg_context */
|
||||
old_context = MemoryContextSwitchTo(agg_context);
|
||||
|
||||
state1 = initArrayResultArr(state2->array_type, InvalidOid,
|
||||
agg_context, false);
|
||||
|
||||
state1->abytes = state2->abytes;
|
||||
state1->data = (char *) palloc(state1->abytes);
|
||||
|
||||
if (state2->nullbitmap)
|
||||
{
|
||||
int size = (state2->aitems + 7) / 8;
|
||||
|
||||
state1->nullbitmap = (bits8 *) palloc(size);
|
||||
memcpy(state1->nullbitmap, state2->nullbitmap, size);
|
||||
}
|
||||
|
||||
memcpy(state1->data, state2->data, state2->nbytes);
|
||||
state1->nbytes = state2->nbytes;
|
||||
state1->aitems = state2->aitems;
|
||||
state1->nitems = state2->nitems;
|
||||
state1->ndims = state2->ndims;
|
||||
memcpy(state1->dims, state2->dims, sizeof(state2->dims));
|
||||
memcpy(state1->lbs, state2->lbs, sizeof(state2->lbs));
|
||||
state1->array_type = state2->array_type;
|
||||
state1->element_type = state2->element_type;
|
||||
|
||||
MemoryContextSwitchTo(old_context);
|
||||
|
||||
PG_RETURN_POINTER(state1);
|
||||
}
|
||||
|
||||
/* We only need to combine the two states if state2 has any items */
|
||||
else if (state2->nitems > 0)
|
||||
{
|
||||
MemoryContext oldContext;
|
||||
int reqsize = state1->nbytes + state2->nbytes;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Check the states are compatible with each other. Ensure we use the
|
||||
* same error messages that are listed in accumArrayResultArr so that
|
||||
* the same error is shown as would have been if we'd not used the
|
||||
* combine function for the aggregation.
|
||||
*/
|
||||
if (state1->ndims != state2->ndims)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
||||
errmsg("cannot accumulate arrays of different dimensionality")));
|
||||
|
||||
/* Check dimensions match ignoring the first dimension. */
|
||||
for (i = 1; i < state1->ndims; i++)
|
||||
{
|
||||
if (state1->dims[i] != state2->dims[i] || state1->lbs[i] != state2->lbs[i])
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
||||
errmsg("cannot accumulate arrays of different dimensionality")));
|
||||
}
|
||||
|
||||
|
||||
oldContext = MemoryContextSwitchTo(state1->mcontext);
|
||||
|
||||
/*
|
||||
* If there's not enough space in state1 then we'll need to reallocate
|
||||
* more.
|
||||
*/
|
||||
if (state1->abytes < reqsize)
|
||||
{
|
||||
/* use a power of 2 size rather than allocating just reqsize */
|
||||
state1->abytes = pg_nextpower2_32(reqsize);
|
||||
state1->data = (char *) repalloc(state1->data, state1->abytes);
|
||||
}
|
||||
|
||||
if (state2->nullbitmap)
|
||||
{
|
||||
int newnitems = state1->nitems + state2->nitems;
|
||||
|
||||
if (state1->nullbitmap == NULL)
|
||||
{
|
||||
/*
|
||||
* First input with nulls; we must retrospectively handle any
|
||||
* previous inputs by marking all their items non-null.
|
||||
*/
|
||||
state1->aitems = pg_nextpower2_32(Max(256, newnitems + 1));
|
||||
state1->nullbitmap = (bits8 *) palloc((state1->aitems + 7) / 8);
|
||||
array_bitmap_copy(state1->nullbitmap, 0,
|
||||
NULL, 0,
|
||||
state1->nitems);
|
||||
}
|
||||
else if (newnitems > state1->aitems)
|
||||
{
|
||||
int newaitems = state1->aitems + state2->aitems;
|
||||
|
||||
state1->aitems = pg_nextpower2_32(newaitems);
|
||||
state1->nullbitmap = (bits8 *)
|
||||
repalloc(state1->nullbitmap, (state1->aitems + 7) / 8);
|
||||
}
|
||||
array_bitmap_copy(state1->nullbitmap, state1->nitems,
|
||||
state2->nullbitmap, 0,
|
||||
state2->nitems);
|
||||
}
|
||||
|
||||
memcpy(state1->data + state1->nbytes, state2->data, state2->nbytes);
|
||||
state1->nbytes += state2->nbytes;
|
||||
state1->nitems += state2->nitems;
|
||||
|
||||
state1->dims[0] += state2->dims[0];
|
||||
/* remaing dims already match, per test above */
|
||||
|
||||
Assert(state1->array_type == state2->array_type);
|
||||
Assert(state1->element_type == state2->element_type);
|
||||
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
}
|
||||
|
||||
PG_RETURN_POINTER(state1);
|
||||
}
|
||||
|
||||
/*
|
||||
* array_agg_array_serialize
|
||||
* Serialize ArrayBuildStateArr into bytea.
|
||||
*/
|
||||
Datum
|
||||
array_agg_array_serialize(PG_FUNCTION_ARGS)
|
||||
{
|
||||
ArrayBuildStateArr *state;
|
||||
StringInfoData buf;
|
||||
bytea *result;
|
||||
|
||||
/* cannot be called directly because of internal-type argument */
|
||||
Assert(AggCheckCallContext(fcinfo, NULL));
|
||||
|
||||
state = (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
|
||||
|
||||
pq_begintypsend(&buf);
|
||||
|
||||
/*
|
||||
* element_type. Putting this first is more convenient in deserialization
|
||||
* so that we can init the new state sooner.
|
||||
*/
|
||||
pq_sendint32(&buf, state->element_type);
|
||||
|
||||
/* array_type */
|
||||
pq_sendint32(&buf, state->array_type);
|
||||
|
||||
/* nbytes */
|
||||
pq_sendint32(&buf, state->nbytes);
|
||||
|
||||
/* data */
|
||||
pq_sendbytes(&buf, state->data, state->nbytes);
|
||||
|
||||
/* abytes */
|
||||
pq_sendint32(&buf, state->abytes);
|
||||
|
||||
/* aitems */
|
||||
pq_sendint32(&buf, state->aitems);
|
||||
|
||||
/* nullbitmap */
|
||||
if (state->nullbitmap)
|
||||
{
|
||||
Assert(state->aitems > 0);
|
||||
pq_sendbytes(&buf, (char *) state->nullbitmap, (state->aitems + 7) / 8);
|
||||
}
|
||||
|
||||
/* nitems */
|
||||
pq_sendint32(&buf, state->nitems);
|
||||
|
||||
/* ndims */
|
||||
pq_sendint32(&buf, state->ndims);
|
||||
|
||||
/* dims: XXX should we just send ndims elements? */
|
||||
pq_sendbytes(&buf, (char *) state->dims, sizeof(state->dims));
|
||||
|
||||
/* lbs */
|
||||
pq_sendbytes(&buf, (char *) state->lbs, sizeof(state->lbs));
|
||||
|
||||
result = pq_endtypsend(&buf);
|
||||
|
||||
PG_RETURN_BYTEA_P(result);
|
||||
}
|
||||
|
||||
Datum
|
||||
array_agg_array_deserialize(PG_FUNCTION_ARGS)
|
||||
{
|
||||
bytea *sstate;
|
||||
ArrayBuildStateArr *result;
|
||||
StringInfoData buf;
|
||||
Oid element_type;
|
||||
Oid array_type;
|
||||
int nbytes;
|
||||
const char *temp;
|
||||
|
||||
/* cannot be called directly because of internal-type argument */
|
||||
Assert(AggCheckCallContext(fcinfo, NULL));
|
||||
|
||||
sstate = PG_GETARG_BYTEA_PP(0);
|
||||
|
||||
/*
|
||||
* Copy the bytea into a StringInfo so that we can "receive" it using the
|
||||
* standard recv-function infrastructure.
|
||||
*/
|
||||
initStringInfo(&buf);
|
||||
appendBinaryStringInfo(&buf,
|
||||
VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate));
|
||||
|
||||
/* element_type */
|
||||
element_type = pq_getmsgint(&buf, 4);
|
||||
|
||||
/* array_type */
|
||||
array_type = pq_getmsgint(&buf, 4);
|
||||
|
||||
/* nbytes */
|
||||
nbytes = pq_getmsgint(&buf, 4);
|
||||
|
||||
result = initArrayResultArr(array_type, element_type,
|
||||
CurrentMemoryContext, false);
|
||||
|
||||
result->abytes = 1024;
|
||||
while (result->abytes < nbytes)
|
||||
result->abytes *= 2;
|
||||
|
||||
result->data = (char *) palloc(result->abytes);
|
||||
|
||||
/* data */
|
||||
temp = pq_getmsgbytes(&buf, nbytes);
|
||||
memcpy(result->data, temp, nbytes);
|
||||
result->nbytes = nbytes;
|
||||
|
||||
/* abytes */
|
||||
result->abytes = pq_getmsgint(&buf, 4);
|
||||
|
||||
/* aitems: might be 0 */
|
||||
result->aitems = pq_getmsgint(&buf, 4);
|
||||
|
||||
/* nullbitmap */
|
||||
if (result->aitems > 0)
|
||||
{
|
||||
int size = (result->aitems + 7) / 8;
|
||||
|
||||
result->nullbitmap = (bits8 *) palloc(size);
|
||||
temp = pq_getmsgbytes(&buf, size);
|
||||
memcpy(result->nullbitmap, temp, size);
|
||||
}
|
||||
else
|
||||
result->nullbitmap = NULL;
|
||||
|
||||
/* nitems */
|
||||
result->nitems = pq_getmsgint(&buf, 4);
|
||||
|
||||
/* ndims */
|
||||
result->ndims = pq_getmsgint(&buf, 4);
|
||||
|
||||
/* dims */
|
||||
temp = pq_getmsgbytes(&buf, sizeof(result->dims));
|
||||
memcpy(result->dims, temp, sizeof(result->dims));
|
||||
|
||||
/* lbs */
|
||||
temp = pq_getmsgbytes(&buf, sizeof(result->lbs));
|
||||
memcpy(result->lbs, temp, sizeof(result->lbs));
|
||||
|
||||
pq_getmsgend(&buf);
|
||||
pfree(buf.data);
|
||||
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
|
||||
Datum
|
||||
array_agg_array_finalfn(PG_FUNCTION_ARGS)
|
||||
{
|
||||
|
Reference in New Issue
Block a user