Fix sharing Agg transition state of DISTINCT or ordered aggs.
authorHeikki Linnakangas
Tue, 20 Dec 2016 07:20:17 +0000 (09:20 +0200)
committerHeikki Linnakangas
Tue, 20 Dec 2016 07:20:17 +0000 (09:20 +0200)
If a query contained two aggregates that could share the transition value,
we would correctly collect the input into a tuplesort only once, but
incorrectly run the transition function over the accumulated input twice,
in finalize_aggregates(). That caused a crash, when we tried to call
tuplesort_performsort() on an already-freed NULL tuplestore.

Backport to 9.6, where sharing of transition state and this bug were
introduced.

Analysis by Tom Lane.

Discussion: https://www.postgresql.org/message-id/ac5b0b69-744c-9114-6218-8300ac920e61@iki.fi

src/backend/executor/nodeAgg.c
src/test/regress/expected/aggregates.out
src/test/regress/sql/aggregates.sql

index a093862f34b7b9fc995e07c2f8cf353b00c429f1..781938e2bb4cf31f075d2ed7f907026e60fa0fd4 100644 (file)
@@ -1575,16 +1575,19 @@ finalize_aggregates(AggState *aggstate,
    Datum      *aggvalues = econtext->ecxt_aggvalues;
    bool       *aggnulls = econtext->ecxt_aggnulls;
    int         aggno;
+   int         transno;
 
    Assert(currentSet == 0 ||
           ((Agg *) aggstate->ss.ps.plan)->aggstrategy != AGG_HASHED);
 
    aggstate->current_set = currentSet;
 
-   for (aggno = 0; aggno < aggstate->numaggs; aggno++)
+   /*
+    * If there were any DISTINCT and/or ORDER BY aggregates, sort their
+    * inputs and run the transition functions.
+    */
+   for (transno = 0; transno < aggstate->numtrans; transno++)
    {
-       AggStatePerAgg peragg = &peraggs[aggno];
-       int         transno = peragg->transno;
        AggStatePerTrans pertrans = &aggstate->pertrans[transno];
        AggStatePerGroup pergroupstate;
 
@@ -1603,6 +1606,18 @@ finalize_aggregates(AggState *aggstate,
                                                pertrans,
                                                pergroupstate);
        }
+   }
+
+   /*
+    * Run the final functions.
+    */
+   for (aggno = 0; aggno < aggstate->numaggs; aggno++)
+   {
+       AggStatePerAgg peragg = &peraggs[aggno];
+       int         transno = peragg->transno;
+       AggStatePerGroup pergroupstate;
+
+       pergroupstate = &pergroup[transno + (currentSet * (aggstate->numtrans))];
 
        if (DO_AGGSPLIT_SKIPFINAL(aggstate->aggsplit))
            finalize_partialaggregate(aggstate, peragg, pergroupstate,
index 45208a6da66bd8d09451c98cfce15a1247c9d00a..fa1f5e787988f7f02fbc095df7359c91c131ef00 100644 (file)
@@ -1816,6 +1816,15 @@ NOTICE:  avg_transfn called with 3
       2 |      4
 (1 row)
 
+-- same as previous one, but with DISTINCT, which requires sorting the input.
+select my_avg(distinct one),my_sum(distinct one) from (values(1),(3),(1)) t(one);
+NOTICE:  avg_transfn called with 1
+NOTICE:  avg_transfn called with 3
+ my_avg | my_sum 
+--------+--------
+      2 |      4
+(1 row)
+
 -- shouldn't share states due to the distinctness not matching.
 select my_avg(distinct one),my_sum(one) from (values(1),(3)) t(one);
 NOTICE:  avg_transfn called with 1
index 430ac49385c7e1723f34a701fcd537fef49f4f8d..2eeb3eedbdf543dc565f40e784fa582efe6cb985 100644 (file)
@@ -727,6 +727,9 @@ select my_avg(one),my_avg(one) from (values(1),(3)) t(one);
 -- aggregate state should be shared as transfn is the same for both aggs.
 select my_avg(one),my_sum(one) from (values(1),(3)) t(one);
 
+-- same as previous one, but with DISTINCT, which requires sorting the input.
+select my_avg(distinct one),my_sum(distinct one) from (values(1),(3),(1)) t(one);
+
 -- shouldn't share states due to the distinctness not matching.
 select my_avg(distinct one),my_sum(one) from (values(1),(3)) t(one);