diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c index c62e3fb..5a3a31d 100644 *** a/src/backend/utils/adt/array_userfuncs.c --- b/src/backend/utils/adt/array_userfuncs.c *************** create_singleton_array(FunctionCallInfo *** 471,477 **** /* ! * ARRAY_AGG aggregate function */ Datum array_agg_transfn(PG_FUNCTION_ARGS) --- 471,477 ---- /* ! * ARRAY_AGG aggregate transition function */ Datum array_agg_transfn(PG_FUNCTION_ARGS) *************** array_agg_transfn(PG_FUNCTION_ARGS) *** 508,513 **** --- 508,537 ---- PG_RETURN_POINTER(state); } + /* + * ARRAY_AGG aggregate inverse transition function + */ + Datum + array_agg_invtransfn(PG_FUNCTION_ARGS) + { + ArrayBuildState *state; + + /* + * Shouldn't happen, but we cannot mark this function strict, since NULLs + * need to be removed just like any other value. + * Must also prevent direct calls because of the "interal" argument + */ + if (PG_ARGISNULL(0)) + elog(ERROR, "array_agg_invtransfn called with NULL state"); + if (!AggCheckCallContext(fcinfo, NULL)) + elog(ERROR, "array_agg_invtransfn called in non-aggregate context"); + + state = (ArrayBuildState *) PG_GETARG_POINTER(0); + shiftArrayResult(state, 1); + + PG_RETURN_POINTER(state); + } + Datum array_agg_finalfn(PG_FUNCTION_ARGS) { diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 311d0c2..828e72d 100644 *** a/src/backend/utils/adt/arrayfuncs.c --- b/src/backend/utils/adt/arrayfuncs.c *************** accumArrayResult(ArrayBuildState *astate *** 4587,4592 **** --- 4587,4593 ---- astate = (ArrayBuildState *) palloc(sizeof(ArrayBuildState)); astate->mcontext = arr_context; astate->alen = 64; /* arbitrary starting array size */ + astate->offset = 0; astate->dvalues = (Datum *) palloc(astate->alen * sizeof(Datum)); astate->dnulls = (bool *) palloc(astate->alen * sizeof(bool)); astate->nelems = 0; *************** accumArrayResult(ArrayBuildState *astate *** 4600,4606 **** { oldcontext = MemoryContextSwitchTo(astate->mcontext); Assert(astate->element_type == element_type); ! /* enlarge dvalues[]/dnulls[] if needed */ if (astate->nelems >= astate->alen) { astate->alen *= 2; --- 4601,4612 ---- { oldcontext = MemoryContextSwitchTo(astate->mcontext); Assert(astate->element_type == element_type); ! /* ! * If the buffers are filled completely (offset must be zero then), ! * we double their size. If they aren't, but the values extend to the ! * end of the buffers, we reclaim wasted space at the beginning by ! * moving the values to the front of the buffers. ! */ if (astate->nelems >= astate->alen) { astate->alen *= 2; *************** accumArrayResult(ArrayBuildState *astate *** 4609,4614 **** --- 4615,4629 ---- astate->dnulls = (bool *) repalloc(astate->dnulls, astate->alen * sizeof(bool)); } + else if (astate->offset + astate->nelems >= astate->alen) + { + memmove(astate->dvalues, astate->dvalues + astate->offset, + astate->alen * sizeof(Datum)); + memmove(astate->dnulls, astate->dnulls + astate->offset, + astate->alen * sizeof(bool)); + astate->offset = 0; + } + Assert(astate->offset + astate->nelems < astate->alen); } /* *************** accumArrayResult(ArrayBuildState *astate *** 4627,4634 **** dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen); } ! astate->dvalues[astate->nelems] = dvalue; ! astate->dnulls[astate->nelems] = disnull; astate->nelems++; MemoryContextSwitchTo(oldcontext); --- 4642,4649 ---- dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen); } ! astate->dvalues[astate->offset + astate->nelems] = dvalue; ! astate->dnulls[astate->offset + astate->nelems] = disnull; astate->nelems++; MemoryContextSwitchTo(oldcontext); *************** accumArrayResult(ArrayBuildState *astate *** 4637,4642 **** --- 4652,4698 ---- } /* + * shiftArrayResult - shift leading Datums out of an array result + * + * astate is working state + * count is the number of leading Datums to shift out + * + * If count is equal to or larger than the number of relements, the array + * result is empty afterwards. If astate is NULL, nothing is done. + */ + void + shiftArrayResult(ArrayBuildState *astate, int count) + { + int i; + + if (astate == NULL) + return; + + /* Limit shift count to number of elements for safety */ + count = Min(count, astate->nelems); + + /* For pass-by-ref types, free values we shift out */ + if (!astate->typbyval) { + for(i = astate->offset; i < astate->offset + count; ++i) { + if (astate->dnulls[i]) + continue; + + pfree(DatumGetPointer(astate->dvalues[i])); + + /* For cleanliness' sake */ + astate->dnulls[i] = false; + astate->dvalues[i] = 0; + } + + } + + /* Update state */ + astate->nelems -= count; + astate->offset += count; + } + + + /* * makeArrayResult - produce 1-D final result of accumArrayResult * * astate is working state (not NULL) *************** makeMdArrayResult(ArrayBuildState *astat *** 4679,4686 **** /* Build the final array result in rcontext */ oldcontext = MemoryContextSwitchTo(rcontext); ! result = construct_md_array(astate->dvalues, ! astate->dnulls, ndims, dims, lbs, --- 4735,4742 ---- /* Build the final array result in rcontext */ oldcontext = MemoryContextSwitchTo(rcontext); ! result = construct_md_array(astate->dvalues + astate->offset, ! astate->dnulls + astate->offset, ndims, dims, lbs, diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c index 8ac402b..768ee49 100644 *** a/src/backend/utils/adt/varlena.c --- b/src/backend/utils/adt/varlena.c *************** typedef struct *** 50,55 **** --- 50,63 ---- int skiptable[256]; /* skip distance for given mismatched char */ } TextPositionState; + typedef struct StringAggState + { + StringInfoData string; /* Contents */ + int offset; /* Offset into stringinfo's data */ + int delimLen; /* Delim length, -1 initially, -2 if multiple */ + int64 notNullCount;/* Number of non-NULL inputs */ + } StringAggState; + #define DatumGetUnknownP(X) ((unknown *) PG_DETOAST_DATUM(X)) #define DatumGetUnknownPCopy(X) ((unknown *) PG_DETOAST_DATUM_COPY(X)) #define PG_GETARG_UNKNOWN_P(n) DatumGetUnknownP(PG_GETARG_DATUM(n)) *************** static void appendStringInfoText(StringI *** 78,84 **** static Datum text_to_array_internal(PG_FUNCTION_ARGS); static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v, const char *fldsep, const char *null_string); ! static StringInfo makeStringAggState(FunctionCallInfo fcinfo); static bool text_format_parse_digits(const char **ptr, const char *end_ptr, int *value); static const char *text_format_parse_format(const char *start_ptr, --- 86,95 ---- static Datum text_to_array_internal(PG_FUNCTION_ARGS); static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v, const char *fldsep, const char *null_string); ! static StringAggState* makeStringAggState(FunctionCallInfo fcinfo); ! static void prepareAppendStringAggState(StringAggState *state, ! int delimLen, int valueLen); ! static bool removeFromStringAggState(StringAggState *state, int valueLen); static bool text_format_parse_digits(const char **ptr, const char *end_ptr, int *value); static const char *text_format_parse_format(const char *start_ptr, *************** static void text_format_string_conversio *** 92,97 **** --- 103,226 ---- static void text_format_append_string(StringInfo buf, const char *str, int flags, int width); + /***************************************************************************** + * SUPPORT ROUTINES FOR STRING_AGG(TEXT) AND STRING_AGG(BYTEA) * + *****************************************************************************/ + + /* + * subroutine to initialize state + */ + static StringAggState* + makeStringAggState(FunctionCallInfo fcinfo) + { + StringAggState* state; + MemoryContext aggcontext; + MemoryContext oldcontext; + + if (!AggCheckCallContext(fcinfo, &aggcontext)) + { + /* cannot be called directly because of internal-type argument */ + elog(ERROR, "string_agg_transfn called in non-aggregate context"); + } + + /* + * Create state in aggregate context. It'll stay there across subsequent + * calls. + */ + oldcontext = MemoryContextSwitchTo(aggcontext); + state = (StringAggState *) palloc(sizeof(StringAggState)); + initStringInfo(&state->string); + state->offset = 0; + state->delimLen = -1; + state->notNullCount = 0; + MemoryContextSwitchTo(oldcontext); + + return state; + } + + /* + * Prepare state for appending a value and a delimiter with specified lengths. + * pass -1 for delimLen if no delimiter will be added + */ + void + prepareAppendStringAggState(StringAggState *state, int delimLen, int valueLen) + { + /* + * Reclaim wasted space + * + * We move the contents to the left if the current contents fit into the + * wasted space, i.e. if we waste more than we store. The limit is + * somewhat arbitrary, but it's the smallest one that allows + * memcpy to be used, because the source and destination don't overlap. + * Note that we must check for <, not <=, because we include the trailing + * '\0' in the copy. + */ + if (state->string.len - state->offset < state->offset) + { + state->string.len -= state->offset; + memcpy(state->string.data, state->string.data + state->offset, + state->string.len + 1); + state->offset = 0; + } + + /* + * Enlarge StringInfo + * + * Not strictly necessary, but avoids potentially resizing twice when + * the actual append... calls are done by the caller + */ + enlargeStringInfo(&state->string, Max(delimLen, 0) + valueLen); + + + /* Track delimiter length */ + if (delimLen == -1) + {} /* Not specified, don't update */ + else if (state->delimLen == -1) + state->delimLen = delimLen; + else if (state->delimLen != delimLen) + state->delimLen = -2; + } + + /* + * Remove value with given length and the delimiter that follows + * + * Returns false if removal was impossible because delimiters varied + */ + bool + removeFromStringAggState(StringAggState *state, int valueLen) + { + /* Remove the string */ + state->offset += valueLen; + + /* + * Remove delimiter if necessary. + * + * The delimiter we need to remove isn't the delimiter we were passed, but + * rather the delimiter passed when adding the input *after* this one. We + * thus need the delimiter length to be all the same to be able to proceed. + * If we're removing the last string, there will be no delimiter following + * it. In that case, we may reset delimLen to its initial value. + */ + if (state->delimLen == -2) + return false; + if (state->offset < state->string.len) + { + Assert(state->delimLen >= 0); + state->offset += state->delimLen; + } + else + state->delimLen = -1; + + /* Don't crash if we're ever asked to remove more than was added */ + if (state->offset > state->string.len) + { + state->offset = state->string.len; + elog(ERROR, "tried to remove more data than was aggregated"); + } + + return true; + } + /***************************************************************************** * CONVERSION ROUTINES EXPORTED FOR USE BY C CODE * *************** byteasend(PG_FUNCTION_ARGS) *** 408,435 **** Datum bytea_string_agg_transfn(PG_FUNCTION_ARGS) { ! StringInfo state; ! state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0); ! /* Append the value unless null. */ ! if (!PG_ARGISNULL(1)) ! { ! bytea *value = PG_GETARG_BYTEA_PP(1); ! /* On the first time through, we ignore the delimiter. */ ! if (state == NULL) ! state = makeStringAggState(fcinfo); ! else if (!PG_ARGISNULL(2)) ! { ! bytea *delim = PG_GETARG_BYTEA_PP(2); ! appendBinaryStringInfo(state, VARDATA_ANY(delim), VARSIZE_ANY_EXHDR(delim)); ! } ! appendBinaryStringInfo(state, VARDATA_ANY(value), VARSIZE_ANY_EXHDR(value)); } /* * The transition type for string_agg() is declared to be "internal", * which is a pass-by-value type the same size as a pointer. --- 537,589 ---- Datum bytea_string_agg_transfn(PG_FUNCTION_ARGS) { ! bytea *value, ! *delim; ! int valueLen, ! delimLen; ! StringAggState *state; ! state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0); ! /* Ignore NULL values, and skip a possible delimiter too. */ ! if (PG_ARGISNULL(1)) ! PG_RETURN_POINTER(state); ! /* Allocate state on first non-NULL value */ ! if (state == NULL) ! state = makeStringAggState(fcinfo); ! value = PG_GETARG_BYTEA_PP(1); ! valueLen = VARSIZE_ANY_EXHDR(value); ! state->notNullCount++; ! Assert(state->offset <= state->string.len); ! if (state->offset == state->string.len) ! { ! /* Buffer is empty, ignore delimiter */ ! delim = NULL; ! delimLen = -1; ! } ! else if (!PG_ARGISNULL(2)) ! { ! /* Delimiter is non-NULL */ ! delim = PG_GETARG_BYTEA_PP(2); ! delimLen = VARSIZE_ANY_EXHDR(delim); ! } ! else ! { ! /* Delimiter is NULL, treat as zero-length string */ ! delim = NULL; ! delimLen = 0; } + /* Append delimiter (if non-NULL) and value */ + prepareAppendStringAggState(state, delimLen, valueLen); + if (delim) + appendBinaryStringInfo(&state->string, VARDATA_ANY(delim), + VARSIZE_ANY_EXHDR(delim)); + appendBinaryStringInfo(&state->string, VARDATA_ANY(value), valueLen); + /* * The transition type for string_agg() is declared to be "internal", * which is a pass-by-value type the same size as a pointer. *************** bytea_string_agg_transfn(PG_FUNCTION_ARG *** 438,459 **** } Datum bytea_string_agg_finalfn(PG_FUNCTION_ARGS) { ! StringInfo state; /* cannot be called directly because of internal-type argument */ Assert(AggCheckCallContext(fcinfo, NULL)); ! state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0); ! if (state != NULL) { bytea *result; ! result = (bytea *) palloc(state->len + VARHDRSZ); ! SET_VARSIZE(result, state->len + VARHDRSZ); ! memcpy(VARDATA(result), state->data, state->len); PG_RETURN_BYTEA_P(result); } else --- 592,661 ---- } Datum + bytea_string_agg_invtransfn(PG_FUNCTION_ARGS) + { + int valueLen; + StringAggState *state; + + /* + * Shouldn't happen, but we cannot mark this function strict, since it + * needs to be able to receive a non-NULL string but a NULL delimiter. + * Must also prevent direct calls because of the "interal" argument + */ + if (PG_ARGISNULL(0)) + elog(ERROR, "string_agg_invtransfn called with NULL state"); + else if (!AggCheckCallContext(fcinfo, NULL)) + elog(ERROR, "string_agg_invtransfn called in non-aggregate context"); + + state = (StringAggState *) PG_GETARG_POINTER(0); + + /* We append nothing if the string is NULL, so skip here as well */ + if (PG_ARGISNULL(1)) + PG_RETURN_POINTER(state); + + /* No need to de-toast value, need only the length */ + valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1)); + Assert(state->notNullCount >= 1); + state->notNullCount--; + + /* + * Attempt to remove value plus following delimiter if possible, return + * NULL otherwise to force an aggregation restart. + * + * The transition type for string_agg() is declared to be "internal", + * which is a pass-by-value type the same size as a pointer. + */ + if (removeFromStringAggState(state, valueLen)) + PG_RETURN_POINTER(state); + else + PG_RETURN_NULL(); + } + + Datum bytea_string_agg_finalfn(PG_FUNCTION_ARGS) { ! StringAggState *state; /* cannot be called directly because of internal-type argument */ Assert(AggCheckCallContext(fcinfo, NULL)); ! state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0); ! /* ! * string_agg() is supposed to return NULL if all inputs were NULL ! * ! * That happens automatically for strict aggregates, but we can't make ! * string_agg() strict because NULL is a valid delimiter value. So we ! * need to keep track of the number of non-NULL 1st arguments ourselves. ! */ ! if (state != NULL && state->notNullCount > 0) { + int resultLen = state->string.len - state->offset; bytea *result; ! result = (bytea *) palloc(resultLen + VARHDRSZ); ! SET_VARSIZE(result, resultLen + VARHDRSZ); ! memcpy(VARDATA(result), state->string.data + state->offset, resultLen); PG_RETURN_BYTEA_P(result); } else *************** pg_column_size(PG_FUNCTION_ARGS) *** 3733,3801 **** * the associated value. */ ! /* subroutine to initialize state */ ! static StringInfo ! makeStringAggState(FunctionCallInfo fcinfo) { ! StringInfo state; ! MemoryContext aggcontext; ! MemoryContext oldcontext; ! if (!AggCheckCallContext(fcinfo, &aggcontext)) { ! /* cannot be called directly because of internal-type argument */ ! elog(ERROR, "string_agg_transfn called in non-aggregate context"); } /* ! * Create state in aggregate context. It'll stay there across subsequent ! * calls. */ ! oldcontext = MemoryContextSwitchTo(aggcontext); ! state = makeStringInfo(); ! MemoryContextSwitchTo(oldcontext); ! ! return state; } Datum ! string_agg_transfn(PG_FUNCTION_ARGS) { ! StringInfo state; ! state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0); ! /* Append the value unless null. */ ! if (!PG_ARGISNULL(1)) ! { ! /* On the first time through, we ignore the delimiter. */ ! if (state == NULL) ! state = makeStringAggState(fcinfo); ! else if (!PG_ARGISNULL(2)) ! appendStringInfoText(state, PG_GETARG_TEXT_PP(2)); /* delimiter */ ! appendStringInfoText(state, PG_GETARG_TEXT_PP(1)); /* value */ ! } /* * The transition type for string_agg() is declared to be "internal", * which is a pass-by-value type the same size as a pointer. */ ! PG_RETURN_POINTER(state); } Datum string_agg_finalfn(PG_FUNCTION_ARGS) { ! StringInfo state; /* cannot be called directly because of internal-type argument */ Assert(AggCheckCallContext(fcinfo, NULL)); ! state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0); ! if (state != NULL) ! PG_RETURN_TEXT_P(cstring_to_text_with_len(state->data, state->len)); else PG_RETURN_NULL(); } --- 3935,4054 ---- * the associated value. */ ! Datum ! string_agg_transfn(PG_FUNCTION_ARGS) { ! text *value, ! *delim; ! int delimLen; ! StringAggState *state; ! state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0); ! ! /* Ignore NULL values, and skip a possible delimiter too. */ ! if (PG_ARGISNULL(1)) ! PG_RETURN_POINTER(state); ! ! /* Allocate state on first non-NULL value */ ! if (state == NULL) ! state = makeStringAggState(fcinfo); ! ! value = PG_GETARG_TEXT_PP(1); ! state->notNullCount++; ! ! Assert(state->offset <= state->string.len); ! if (state->offset == state->string.len) { ! /* Buffer is empty, ignore delimiter */ ! delim = NULL; ! delimLen = -1; ! } ! else if (!PG_ARGISNULL(2)) ! { ! /* Delimiter is non-NULL */ ! delim = PG_GETARG_TEXT_PP(2); ! delimLen = VARSIZE_ANY_EXHDR(delim); } + else + { + /* Delimiter is NULL, treat as zero-length string */ + delim = NULL; + delimLen = 0; + } + + /* Append delimiter (if non-NULL) and value */ + prepareAppendStringAggState(state, delimLen, VARSIZE_ANY_EXHDR(value)); + if (delim) + appendStringInfoText(&state->string, delim); + appendStringInfoText(&state->string, value); /* ! * The transition type for string_agg() is declared to be "internal", ! * which is a pass-by-value type the same size as a pointer. */ ! PG_RETURN_POINTER(state); } Datum ! string_agg_invtransfn(PG_FUNCTION_ARGS) { ! int valueLen; ! StringAggState *state; ! /* ! * Shouldn't happen, but we cannot mark this function strict, since it ! * needs to be able to receive a non-NULL string but a NULL delimiter. ! * Must also prevent direct calls because of the "interal" argument ! */ ! if (PG_ARGISNULL(0)) ! elog(ERROR, "string_agg_invtransfn called with NULL state"); ! else if (!AggCheckCallContext(fcinfo, NULL)) ! elog(ERROR, "string_agg_invtransfn called in non-aggregate context"); ! state = (StringAggState *) PG_GETARG_POINTER(0); ! /* We append nothing if the string is NULL, so skip here as well */ ! if (PG_ARGISNULL(1)) ! PG_RETURN_POINTER(state); ! ! /* No need to de-toast value, need only the length */ ! valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1)); ! Assert(state->notNullCount >= 1); ! state->notNullCount--; /* + * Attempt to remove value plus following delimiter if possible, return + * NULL otherwise to force an aggregation restart. + * * The transition type for string_agg() is declared to be "internal", * which is a pass-by-value type the same size as a pointer. */ ! if (removeFromStringAggState(state, valueLen)) ! PG_RETURN_POINTER(state); ! else ! PG_RETURN_NULL(); } Datum string_agg_finalfn(PG_FUNCTION_ARGS) { ! StringAggState *state; /* cannot be called directly because of internal-type argument */ Assert(AggCheckCallContext(fcinfo, NULL)); ! state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0); ! /* ! * string_agg() is supposed to return NULL if all inputs were NULL ! * ! * That happens automatically for strict aggregates, but we can't make ! * string_agg() strict because NULL is a valid delimiter value. So we ! * need to keep track of the number of non-NULL 1st arguments ourselves. ! */ ! if (state != NULL && state->notNullCount > 0) ! PG_RETURN_TEXT_P(cstring_to_text_with_len(state->string.data + state->offset, ! state->string.len - state->offset)); else PG_RETURN_NULL(); } diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h index 2fb3871..5c9488f 100644 *** a/src/include/catalog/pg_aggregate.h --- b/src/include/catalog/pg_aggregate.h *************** DATA(insert ( 2243 n 0 bitor - - 0 *** 250,262 **** DATA(insert ( 2901 n 0 xmlconcat2 - - 0 142 0 _null_ )); /* array */ ! DATA(insert ( 2335 n 0 array_agg_transfn - array_agg_finalfn 0 2281 0 _null_ )); /* text */ ! DATA(insert ( 3538 n 0 string_agg_transfn - string_agg_finalfn 0 2281 0 _null_ )); /* bytea */ ! DATA(insert ( 3545 n 0 bytea_string_agg_transfn - bytea_string_agg_finalfn 0 2281 0 _null_ )); /* json */ DATA(insert ( 3175 n 0 json_agg_transfn - json_agg_finalfn 0 2281 0 _null_ )); --- 250,262 ---- DATA(insert ( 2901 n 0 xmlconcat2 - - 0 142 0 _null_ )); /* array */ ! DATA(insert ( 2335 n 0 array_agg_transfn array_agg_invtransfn array_agg_finalfn 0 2281 0 _null_ )); /* text */ ! DATA(insert ( 3538 n 0 string_agg_transfn string_agg_invtransfn string_agg_finalfn 0 2281 0 _null_ )); /* bytea */ ! DATA(insert ( 3545 n 0 bytea_string_agg_transfn bytea_string_agg_invtransfn bytea_string_agg_finalfn 0 2281 0 _null_ )); /* json */ DATA(insert ( 3175 n 0 json_agg_transfn - json_agg_finalfn 0 2281 0 _null_ )); diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index ad9774c..1c2d8bb 100644 *** a/src/include/catalog/pg_proc.h --- b/src/include/catalog/pg_proc.h *************** DATA(insert OID = 3168 ( array_replace *** 875,880 **** --- 875,882 ---- DESCR("replace any occurrences of an element in an array"); DATA(insert OID = 2333 ( array_agg_transfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_transfn _null_ _null_ _null_ )); DESCR("aggregate transition function"); + DATA(insert OID = 3180 ( array_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_invtransfn _null_ _null_ _null_ )); + DESCR("aggregate transition function"); DATA(insert OID = 2334 ( array_agg_finalfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2277 "2281" _null_ _null_ _null_ _null_ array_agg_finalfn _null_ _null_ _null_ )); DESCR("aggregate final function"); DATA(insert OID = 2335 ( array_agg PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 2277 "2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); *************** DESCR("aggregate final function"); *** 2463,2474 **** --- 2465,2480 ---- DATA(insert OID = 3535 ( string_agg_transfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_transfn _null_ _null_ _null_ )); DESCR("aggregate transition function"); + DATA(insert OID = 3546 ( string_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_invtransfn _null_ _null_ _null_ )); + DESCR("aggregate transition function"); DATA(insert OID = 3536 ( string_agg_finalfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 25 "2281" _null_ _null_ _null_ _null_ string_agg_finalfn _null_ _null_ _null_ )); DESCR("aggregate final function"); DATA(insert OID = 3538 ( string_agg PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); DESCR("concatenate aggregate input into a string"); DATA(insert OID = 3543 ( bytea_string_agg_transfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_transfn _null_ _null_ _null_ )); DESCR("aggregate transition function"); + DATA(insert OID = 3547 ( bytea_string_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_invtransfn _null_ _null_ _null_ )); + DESCR("aggregate transition function"); DATA(insert OID = 3544 ( bytea_string_agg_finalfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 17 "2281" _null_ _null_ _null_ _null_ bytea_string_agg_finalfn _null_ _null_ _null_ )); DESCR("aggregate final function"); DATA(insert OID = 3545 ( string_agg PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 17 "17 17" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); diff --git a/src/include/utils/array.h b/src/include/utils/array.h index 9bbfaae..9a6fc39 100644 *** a/src/include/utils/array.h --- b/src/include/utils/array.h *************** typedef struct ArrayBuildState *** 83,88 **** --- 83,89 ---- Datum *dvalues; /* array of accumulated Datums */ bool *dnulls; /* array of is-null flags for Datums */ int alen; /* allocated length of above arrays */ + int offset; /* offset of first element in above arrays */ int nelems; /* number of valid entries in above arrays */ Oid element_type; /* data type of the Datums */ int16 typlen; /* needed info about datatype */ *************** extern ArrayBuildState *accumArrayResult *** 255,260 **** --- 256,262 ---- Datum dvalue, bool disnull, Oid element_type, MemoryContext rcontext); + extern void shiftArrayResult(ArrayBuildState *astate, int count); extern Datum makeArrayResult(ArrayBuildState *astate, MemoryContext rcontext); extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims, *************** extern ArrayType *create_singleton_array *** 290,295 **** --- 292,298 ---- int ndims); extern Datum array_agg_transfn(PG_FUNCTION_ARGS); + extern Datum array_agg_invtransfn(PG_FUNCTION_ARGS); extern Datum array_agg_finalfn(PG_FUNCTION_ARGS); /* diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index b90d88d..7d8e749 100644 *** a/src/include/utils/builtins.h --- b/src/include/utils/builtins.h *************** extern Datum unknownsend(PG_FUNCTION_ARG *** 812,820 **** --- 812,822 ---- extern Datum pg_column_size(PG_FUNCTION_ARGS); extern Datum bytea_string_agg_transfn(PG_FUNCTION_ARGS); + extern Datum bytea_string_agg_invtransfn(PG_FUNCTION_ARGS); extern Datum bytea_string_agg_finalfn(PG_FUNCTION_ARGS); extern Datum string_agg_transfn(PG_FUNCTION_ARGS); extern Datum string_agg_finalfn(PG_FUNCTION_ARGS); + extern Datum string_agg_invtransfn(PG_FUNCTION_ARGS); extern Datum text_concat(PG_FUNCTION_ARGS); extern Datum text_concat_ws(PG_FUNCTION_ARGS); diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out index df676ad..81a4b74 100644 *** a/src/test/regress/expected/window.out --- b/src/test/regress/expected/window.out *************** DROP FUNCTION logging_sfunc_nonstrict(te *** 1288,1290 **** --- 1288,1334 ---- -- Tests of the collecting inverse transition functions -- -- + SELECT + i::text || ':' || COALESCE(s, '-') || ',' || + COALESCE(encode(b, 'hex'), '----') || ',' || + COALESCE(n::text, '-') AS row, + coalesce(string_agg(s,'') OVER wnd, '-') AS str, + coalesce(string_agg(s,',') OVER wnd, '-') AS str_del, + coalesce(string_agg(s, nullif(repeat('|', i%3), '')) + OVER wnd, '-') AS str_vardel, + coalesce((string_agg(b,'') OVER wnd)::text, '-') AS bin, + coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del, + coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), '')) + OVER wnd)::text, '-') AS bin_vardel, + array_agg(n) OVER wnd AS ary + FROM (VALUES + (1, '1', E'\\x0100'::bytea, NULL::int8), + (2, NULL, E'\\x0200'::bytea, 2::int8), + (3, '3', NULL, 3::int8), + (4, NULL, E'\\x0400'::bytea, 4::int8), + (5, '5', NULL, NULL::int8), + (6, '6', E'\\x0600'::bytea, 6::int8), + (7, '7', E'\\x0700'::bytea, NULL::int8), + (8, '8', E'\\x0800'::bytea, 8::int8), + (9, NULL, NULL, NULL), + (10, NULL, NULL, NULL), + (11, NULL, NULL, NULL), + (12, NULL, NULL, NULL) + ) AS v(i, s, b, n) + WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING); + row | str | str_del | str_vardel | bin | bin_del | bin_vardel | ary + -------------+-----+---------+------------+----------------+--------------------+----------------------+------------------ + 1:1,0100,- | - | - | - | - | - | - | + 2:-,0200,2 | 1 | 1 | 1 | \x0100 | \x0100 | \x0100 | {NULL} + 3:3,----,3 | 1 | 1 | 1 | \x01000200 | \x0100000200 | \x010000000200 | {NULL,2} + 4:-,0400,4 | 13 | 1,3 | 13 | \x01000200 | \x0100000200 | \x010000000200 | {NULL,2,3} + 5:5,----,- | 3 | 3 | 3 | \x02000400 | \x0200000400 | \x0200000400 | {2,3,4} + 6:6,0600,6 | 35 | 3,5 | 3||5 | \x0400 | \x0400 | \x0400 | {3,4,NULL} + 7:7,0700,- | 56 | 5,6 | 56 | \x04000600 | \x0400000600 | \x04000600 | {4,NULL,6} + 8:8,0800,8 | 567 | 5,6,7 | 56|7 | \x06000700 | \x0600000700 | \x0600000700 | {NULL,6,NULL} + 9:-,----,- | 678 | 6,7,8 | 6|7||8 | \x060007000800 | \x0600000700000800 | \x060000070000000800 | {6,NULL,8} + 10:-,----,- | 78 | 7,8 | 7||8 | \x07000800 | \x0700000800 | \x070000000800 | {NULL,8,NULL} + 11:-,----,- | 8 | 8 | 8 | \x0800 | \x0800 | \x0800 | {8,NULL,NULL} + 12:-,----,- | - | - | - | - | - | - | {NULL,NULL,NULL} + (12 rows) + diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql index 65c1005..d7c3f27 100644 *** a/src/test/regress/sql/window.sql --- b/src/test/regress/sql/window.sql *************** DROP FUNCTION logging_sfunc_nonstrict(te *** 476,478 **** --- 476,507 ---- -- Tests of the collecting inverse transition functions -- -- + + SELECT + i::text || ':' || COALESCE(s, '-') || ',' || + COALESCE(encode(b, 'hex'), '----') || ',' || + COALESCE(n::text, '-') AS row, + coalesce(string_agg(s,'') OVER wnd, '-') AS str, + coalesce(string_agg(s,',') OVER wnd, '-') AS str_del, + coalesce(string_agg(s, nullif(repeat('|', i%3), '')) + OVER wnd, '-') AS str_vardel, + coalesce((string_agg(b,'') OVER wnd)::text, '-') AS bin, + coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del, + coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), '')) + OVER wnd)::text, '-') AS bin_vardel, + array_agg(n) OVER wnd AS ary + FROM (VALUES + (1, '1', E'\\x0100'::bytea, NULL::int8), + (2, NULL, E'\\x0200'::bytea, 2::int8), + (3, '3', NULL, 3::int8), + (4, NULL, E'\\x0400'::bytea, 4::int8), + (5, '5', NULL, NULL::int8), + (6, '6', E'\\x0600'::bytea, 6::int8), + (7, '7', E'\\x0700'::bytea, NULL::int8), + (8, '8', E'\\x0800'::bytea, 8::int8), + (9, NULL, NULL, NULL), + (10, NULL, NULL, NULL), + (11, NULL, NULL, NULL), + (12, NULL, NULL, NULL) + ) AS v(i, s, b, n) + WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING);