Fix things so that array_agg_finalfn does not modify or free its input
authorTom Lane <[email protected]>
Sat, 20 Jun 2009 18:45:28 +0000 (18:45 +0000)
committerTom Lane <[email protected]>
Sat, 20 Jun 2009 18:45:28 +0000 (18:45 +0000)
ArrayBuildState, per trouble report from Merlin Moncure.  By adopting
this fix, we are essentially deciding that aggregate final-functions
should not modify their inputs ever.  Adjust documentation and comments
to match that conclusion.

doc/src/sgml/xaggr.sgml
src/backend/executor/nodeWindowAgg.c
src/backend/utils/adt/array_userfuncs.c
src/backend/utils/adt/arrayfuncs.c

index 6be1ade78d1f5aab5342c376e902ec0365d72712..364982792a0732f6a607c1536c32fcfc77171963 100644 (file)
@@ -175,10 +175,14 @@ SELECT attrelid::regclass, array_accum(atttypid::regtype)
             (IsA(fcinfo-&gt;context, AggState) ||
              IsA(fcinfo-&gt;context, WindowAggState)))
 </programlisting>
-   One reason for checking this is that when it is true, the first input
+   One reason for checking this is that when it is true for a transition
+   function, the first input
    must be a temporary transition value and can therefore safely be modified
    in-place rather than allocating a new copy.  (This is the <emphasis>only</>
-   case where it is safe for a function to modify a pass-by-reference input.)
+   case where it is safe for a function to modify a pass-by-reference input.
+   In particular, aggregate final functions should not modify their inputs in
+   any case, because in some cases they will be re-executed on the same
+   final transition value.)
    See <literal>int8inc()</> for an example.
   </para>
 
index 79f0c201ee3a8b7a4f5f33237306396bde574f35..6674f67ac0484892ed88c8b9e9d2479068bc3753 100644 (file)
@@ -410,9 +410,8 @@ eval_windowaggregates(WindowAggState *winstate)
         * need the current aggregate value.  This is considerably more efficient
         * than the naive approach of re-running the entire aggregate calculation
         * for each current row.  It does assume that the final function doesn't
-        * damage the running transition value.  (Some C-coded aggregates do that
-        * for efficiency's sake --- but they are supposed to do so only when
-        * their fcinfo->context is an AggState, not a WindowAggState.)
+        * damage the running transition value, but we have the same assumption
+        * in nodeAgg.c too (when it rescans an existing hash table).
         *
         * In many common cases, multiple rows share the same frame and hence the
         * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
index 6d610506db5afed226bcbcb4ddf7594cfc4327d6..aa219f585bfb9d959195f3f839e4f3b1bb407533 100644 (file)
@@ -537,10 +537,13 @@ array_agg_finalfn(PG_FUNCTION_ARGS)
        dims[0] = state->nelems;
        lbs[0] = 1;
 
-       /* Release working state if regular aggregate, but not if window agg */
+       /*
+        * Make the result.  We cannot release the ArrayBuildState because
+        * sometimes aggregate final functions are re-executed.
+        */
        result = makeMdArrayResult(state, 1, dims, lbs,
                                                           CurrentMemoryContext,
-                                                          IsA(fcinfo->context, AggState));
+                                                          false);
 
        PG_RETURN_DATUM(result);
 }
index 480b6490d363bbfc903718b720fb53f36765a4da..fb96541bdfc9e42ee0e3515248c2e22c5da9ebb2 100644 (file)
@@ -4179,9 +4179,21 @@ accumArrayResult(ArrayBuildState *astate,
                }
        }
 
-       /* Use datumCopy to ensure pass-by-ref stuff is copied into mcontext */
+       /*
+        * Ensure pass-by-ref stuff is copied into mcontext; and detoast it too
+        * if it's varlena.  (You might think that detoasting is not needed here
+        * because construct_md_array can detoast the array elements later.
+        * However, we must not let construct_md_array modify the ArrayBuildState
+        * because that would mean array_agg_finalfn damages its input, which
+        * is verboten.  Also, this way frequently saves one copying step.)
+        */
        if (!disnull && !astate->typbyval)
-               dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
+       {
+               if (astate->typlen == -1)
+                       dvalue = PointerGetDatum(PG_DETOAST_DATUM_COPY(dvalue));
+               else
+                       dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
+       }
 
        astate->dvalues[astate->nelems] = dvalue;
        astate->dnulls[astate->nelems] = disnull;