diff options
Diffstat (limited to 'src/backend/utils/adt')
-rw-r--r-- | src/backend/utils/adt/orderedsetaggs.c | 133 |
1 files changed, 73 insertions, 60 deletions
diff --git a/src/backend/utils/adt/orderedsetaggs.c b/src/backend/utils/adt/orderedsetaggs.c index 25905a3287e..1e323d94445 100644 --- a/src/backend/utils/adt/orderedsetaggs.c +++ b/src/backend/utils/adt/orderedsetaggs.c @@ -40,14 +40,22 @@ * create just once per query because they will not change across groups. * The per-query struct and subsidiary data live in the executor's per-query * memory context, and go away implicitly at ExecutorEnd(). + * + * These structs are set up during the first call of the transition function. + * Because we allow nodeAgg.c to merge ordered-set aggregates (but not + * hypothetical aggregates) with identical inputs and transition functions, + * this info must not depend on the particular aggregate (ie, particular + * final-function), nor on the direct argument(s) of the aggregate. */ typedef struct OSAPerQueryState { - /* Aggref for this aggregate: */ + /* Representative Aggref for this aggregate: */ Aggref *aggref; /* Memory context containing this struct and other per-query data: */ MemoryContext qcontext; + /* Do we expect multiple final-function calls within one group? */ + bool rescan_needed; /* These fields are used only when accumulating tuples: */ @@ -91,6 +99,8 @@ typedef struct OSAPerGroupState Tuplesortstate *sortstate; /* Number of normal rows inserted into sortstate: */ int64 number_of_rows; + /* Have we already done tuplesort_performsort? */ + bool sort_done; } OSAPerGroupState; static void ordered_set_shutdown(Datum arg); @@ -146,6 +156,9 @@ ordered_set_startup(FunctionCallInfo fcinfo, bool use_tuples) qstate->aggref = aggref; qstate->qcontext = qcontext; + /* We need to support rescans if the trans state is shared */ + qstate->rescan_needed = AggStateIsShared(fcinfo); + /* Extract the sort information */ sortlist = aggref->aggorder; numSortCols = list_length(sortlist); @@ -277,15 +290,18 @@ ordered_set_startup(FunctionCallInfo fcinfo, bool use_tuples) qstate->sortOperators, qstate->sortCollations, qstate->sortNullsFirsts, - work_mem, false); + work_mem, + qstate->rescan_needed); else osastate->sortstate = tuplesort_begin_datum(qstate->sortColType, qstate->sortOperator, qstate->sortCollation, qstate->sortNullsFirst, - work_mem, false); + work_mem, + qstate->rescan_needed); osastate->number_of_rows = 0; + osastate->sort_done = false; /* Now register a shutdown callback to clean things up at end of group */ AggRegisterCallback(fcinfo, @@ -306,14 +322,12 @@ ordered_set_startup(FunctionCallInfo fcinfo, bool use_tuples) * group) by ExecutorEnd. But we must take care to release any potential * non-memory resources. * - * This callback is arguably unnecessary, since we don't support use of - * ordered-set aggs in AGG_HASHED mode and there is currently no non-error - * code path in non-hashed modes wherein nodeAgg.c won't call the finalfn - * after calling the transfn one or more times. So in principle we could rely - * on the finalfn to delete the tuplestore etc. However, it's possible that - * such a code path might exist in future, and in any case it'd be - * notationally tedious and sometimes require extra data copying to ensure - * we always delete the tuplestore in the finalfn. + * In the case where we're not expecting multiple finalfn calls, we could + * arguably rely on the finalfn to clean up; but it's easier and more testable + * if we just do it the same way in either case. Note that many of the + * finalfns could *not* free the tuplesort object, at least not without extra + * data copying, because what they return is a pointer to a datum inside the + * tuplesort object. */ static void ordered_set_shutdown(Datum arg) @@ -436,8 +450,14 @@ percentile_disc_final(PG_FUNCTION_ARGS) if (osastate->number_of_rows == 0) PG_RETURN_NULL(); - /* Finish the sort */ - tuplesort_performsort(osastate->sortstate); + /* Finish the sort, or rescan if we already did */ + if (!osastate->sort_done) + { + tuplesort_performsort(osastate->sortstate); + osastate->sort_done = true; + } + else + tuplesort_rescan(osastate->sortstate); /*---------- * We need the smallest K such that (K/N) >= percentile. @@ -457,13 +477,6 @@ percentile_disc_final(PG_FUNCTION_ARGS) if (!tuplesort_getdatum(osastate->sortstate, true, &val, &isnull, NULL)) elog(ERROR, "missing row in percentile_disc"); - /* - * Note: we *cannot* clean up the tuplesort object here, because the value - * to be returned is allocated inside its sortcontext. We could use - * datumCopy to copy it out of there, but it doesn't seem worth the - * trouble, since the cleanup callback will clear the tuplesort later. - */ - /* We shouldn't have stored any nulls, but do the right thing anyway */ if (isnull) PG_RETURN_NULL(); @@ -543,8 +556,14 @@ percentile_cont_final_common(FunctionCallInfo fcinfo, Assert(expect_type == osastate->qstate->sortColType); - /* Finish the sort */ - tuplesort_performsort(osastate->sortstate); + /* Finish the sort, or rescan if we already did */ + if (!osastate->sort_done) + { + tuplesort_performsort(osastate->sortstate); + osastate->sort_done = true; + } + else + tuplesort_rescan(osastate->sortstate); first_row = floor(percentile * (osastate->number_of_rows - 1)); second_row = ceil(percentile * (osastate->number_of_rows - 1)); @@ -575,13 +594,6 @@ percentile_cont_final_common(FunctionCallInfo fcinfo, val = lerpfunc(first_val, second_val, proportion); } - /* - * Note: we *cannot* clean up the tuplesort object here, because the value - * to be returned may be allocated inside its sortcontext. We could use - * datumCopy to copy it out of there, but it doesn't seem worth the - * trouble, since the cleanup callback will clear the tuplesort later. - */ - PG_RETURN_DATUM(val); } @@ -779,8 +791,14 @@ percentile_disc_multi_final(PG_FUNCTION_ARGS) */ if (i < num_percentiles) { - /* Finish the sort */ - tuplesort_performsort(osastate->sortstate); + /* Finish the sort, or rescan if we already did */ + if (!osastate->sort_done) + { + tuplesort_performsort(osastate->sortstate); + osastate->sort_done = true; + } + else + tuplesort_rescan(osastate->sortstate); for (; i < num_percentiles; i++) { @@ -804,11 +822,6 @@ percentile_disc_multi_final(PG_FUNCTION_ARGS) } } - /* - * We could clean up the tuplesort object after forming the array, but - * probably not worth the trouble. - */ - /* We make the output array the same shape as the input */ PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull, ARR_NDIM(param), @@ -902,8 +915,14 @@ percentile_cont_multi_final_common(FunctionCallInfo fcinfo, */ if (i < num_percentiles) { - /* Finish the sort */ - tuplesort_performsort(osastate->sortstate); + /* Finish the sort, or rescan if we already did */ + if (!osastate->sort_done) + { + tuplesort_performsort(osastate->sortstate); + osastate->sort_done = true; + } + else + tuplesort_rescan(osastate->sortstate); for (; i < num_percentiles; i++) { @@ -962,11 +981,6 @@ percentile_cont_multi_final_common(FunctionCallInfo fcinfo, } } - /* - * We could clean up the tuplesort object after forming the array, but - * probably not worth the trouble. - */ - /* We make the output array the same shape as the input */ PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull, ARR_NDIM(param), @@ -1043,8 +1057,14 @@ mode_final(PG_FUNCTION_ARGS) shouldfree = !(osastate->qstate->typByVal); - /* Finish the sort */ - tuplesort_performsort(osastate->sortstate); + /* Finish the sort, or rescan if we already did */ + if (!osastate->sort_done) + { + tuplesort_performsort(osastate->sortstate); + osastate->sort_done = true; + } + else + tuplesort_rescan(osastate->sortstate); /* Scan tuples and count frequencies */ while (tuplesort_getdatum(osastate->sortstate, true, &val, &isnull, &abbrev_val)) @@ -1097,13 +1117,6 @@ mode_final(PG_FUNCTION_ARGS) if (shouldfree && !last_val_is_mode) pfree(DatumGetPointer(last_val)); - /* - * Note: we *cannot* clean up the tuplesort object here, because the value - * to be returned is allocated inside its sortcontext. We could use - * datumCopy to copy it out of there, but it doesn't seem worth the - * trouble, since the cleanup callback will clear the tuplesort later. - */ - if (mode_freq) PG_RETURN_DATUM(mode_val); else @@ -1174,6 +1187,9 @@ hypothetical_rank_common(FunctionCallInfo fcinfo, int flag, hypothetical_check_argtypes(fcinfo, nargs, osastate->qstate->tupdesc); + /* because we need a hypothetical row, we can't share transition state */ + Assert(!osastate->sort_done); + /* insert the hypothetical row into the sort */ slot = osastate->qstate->tupslot; ExecClearTuple(slot); @@ -1190,6 +1206,7 @@ hypothetical_rank_common(FunctionCallInfo fcinfo, int flag, /* finish the sort */ tuplesort_performsort(osastate->sortstate); + osastate->sort_done = true; /* iterate till we find the hypothetical row */ while (tuplesort_gettupleslot(osastate->sortstate, true, true, slot, NULL)) @@ -1207,10 +1224,6 @@ hypothetical_rank_common(FunctionCallInfo fcinfo, int flag, ExecClearTuple(slot); - /* Might as well clean up the tuplesort object immediately */ - tuplesort_end(osastate->sortstate); - osastate->sortstate = NULL; - return rank; } @@ -1329,6 +1342,9 @@ hypothetical_dense_rank_final(PG_FUNCTION_ARGS) /* Get short-term context we can use for execTuplesMatch */ tmpcontext = AggGetTempMemoryContext(fcinfo); + /* because we need a hypothetical row, we can't share transition state */ + Assert(!osastate->sort_done); + /* insert the hypothetical row into the sort */ slot = osastate->qstate->tupslot; ExecClearTuple(slot); @@ -1345,6 +1361,7 @@ hypothetical_dense_rank_final(PG_FUNCTION_ARGS) /* finish the sort */ tuplesort_performsort(osastate->sortstate); + osastate->sort_done = true; /* * We alternate fetching into tupslot and extraslot so that we have the @@ -1391,10 +1408,6 @@ hypothetical_dense_rank_final(PG_FUNCTION_ARGS) ExecDropSingleTupleTableSlot(extraslot); - /* Might as well clean up the tuplesort object immediately */ - tuplesort_end(osastate->sortstate); - osastate->sortstate = NULL; - rank = rank - duplicate_count; PG_RETURN_INT64(rank); |