-<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.492 2009/11/24 19:21:15 petere Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.493 2009/12/15 17:57:46 tgl Exp $ -->
 
  <chapter id="functions">
   <title>Functions and Operators</title>
      The function <function>xmlagg</function> is, unlike the other
      functions described here, an aggregate function.  It concatenates the
      input values to the aggregate function call,
-     like <function>xmlconcat</function> does.
+     much like <function>xmlconcat</function> does, except that concatenation
+     occurs across rows rather than across expressions in a single row.
      See <xref linkend="functions-aggregate"> for additional information
      about aggregate functions.
     </para>
     </para>
 
     <para>
-     To determine the order of the concatenation, something like the
-     following approach can be used:
+     To determine the order of the concatenation, an <literal>ORDER BY</>
+     clause may be added to the aggregate call as described in
+     <xref linkend="syntax-aggregates">. For example:
 
 <screen><![CDATA[
-SELECT xmlagg(x) FROM (SELECT * FROM test ORDER BY y DESC) AS tab;
+SELECT xmlagg(x ORDER BY y DESC) FROM test;
         xmlagg
 ----------------------
  <bar/><foo>abc</foo>
 ]]></screen>
+    </para>
 
-     Again, see <xref linkend="functions-aggregate"> for additional
-     information.
+    <para>
+     The following non-standard approach used to be recommended
+     in previous versions, and may still be useful in specific
+     cases:
+
+<screen><![CDATA[
+SELECT xmlagg(x) FROM (SELECT * FROM test ORDER BY y DESC) AS tab;
+        xmlagg
+----------------------
+ <bar/><foo>abc</foo>
+]]></screen>
     </para>
    </sect3>
 
    The aggregate functions <function>array_agg</function>
    and <function>xmlagg</function>, as well as similar user-defined
    aggregate functions, produce meaningfully different result values
-   depending on the order of the input values.  In the current
-   implementation, the order of the input is in principle unspecified.
-   Supplying the input values from a sorted subquery
-   will usually work, however.  For example:
+   depending on the order of the input values.  This ordering is
+   unspecified by default, but can be controlled by writing an
+   <literal>ORDER BY</> clause within the aggregate call, as shown in
+   <xref linkend="syntax-aggregates">.
+   Alternatively, supplying the input values from a sorted subquery
+   will usually work.  For example:
 
 <screen><![CDATA[
 SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab;
 ]]></screen>
 
    But this syntax is not allowed in the SQL standard, and is
-   not portable to other database systems.  A future version of
-   <productname>PostgreSQL</> might provide an additional feature to control
-   the order in a better-defined way (<literal>xmlagg(expr ORDER BY expr, expr,
-   ...)</literal>).
+   not portable to other database systems.
   </para>
 
   <para>
 
-<!-- $PostgreSQL: pgsql/doc/src/sgml/syntax.sgml,v 1.138 2009/11/05 23:24:22 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/syntax.sgml,v 1.139 2009/12/15 17:57:46 tgl Exp $ -->
 
 <chapter id="sql-syntax">
  <title>SQL Syntax</title>
     syntax of an aggregate expression is one of the following:
 
 <synopsis>
-<replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] )
-<replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] )
-<replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable>)
+<replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
+<replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
+<replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
 <replaceable>aggregate_name</replaceable> ( * )
 </synopsis>
 
     where <replaceable>aggregate_name</replaceable> is a previously
-    defined aggregate (possibly qualified with a schema name), and
+    defined aggregate (possibly qualified with a schema name),
     <replaceable>expression</replaceable> is
     any value expression that does not itself contain an aggregate
-    expression or a window function call.
+    expression or a window function call, and
+    <replaceable>order_by_clause</replaceable> is a optional
+    <literal>ORDER BY</> clause as described below.
    </para>
 
    <para>
     whether to ignore null values or not — but all the standard ones do.)
     The second form is the same as the first, since
     <literal>ALL</literal> is the default.  The third form invokes the
-    aggregate for all distinct non-null values of the expressions found
-    in the input rows.  The last form invokes the aggregate once for
+    aggregate for all distinct values of the expressions found
+    in the input rows (ignoring nulls if the function chooses to do so).
+    The last form invokes the aggregate once for
     each input row regardless of null or non-null values; since no
     particular input value is specified, it is generally only useful
     for the <function>count(*)</function> aggregate function.
     distinct non-null values of <literal>f1</literal>.
    </para>
 
+   <para>
+    Ordinarily, the input rows are fed to the aggregate function in an
+    unspecified order.  In many cases this does not matter; for example,
+    <function>min</> produces the same result no matter what order it
+    receives the inputs in.  However, some aggregate functions
+    (such as <function>array_agg</> and <function>xmlagg</>) produce
+    results that depend on the ordering of the input rows.  When using
+    such an aggregate, the optional <replaceable>order_by_clause</> can be
+    used to specify the desired ordering.  The <replaceable>order_by_clause</>
+    has the same syntax as for a query-level <literal>ORDER BY</> clause, as
+    described in <xref linkend="queries-order">, except that its expressions
+    are always just expressions and cannot be output-column names or numbers.
+    For example:
+
+<programlisting>
+SELECT array_agg(a ORDER BY b DESC) FROM table;
+</programlisting>
+   </para>
+
+   <para>
+    If <literal>DISTINCT</> is specified in addition to an
+    <replaceable>order_by_clause</>, then all the <literal>ORDER BY</>
+    expressions must match regular arguments of the aggregate; that is,
+    you cannot sort on an expression that is not included in the
+    <literal>DISTINCT</> list.
+   </para>
+
+   <note>
+    <para>
+     The ability to specify both <literal>DISTINCT</> and <literal>ORDER BY</>
+     in an aggregate function is a <productname>PostgreSQL</> extension.
+    </para>
+   </note>
+
    <para>
     The predefined aggregate functions are described in <xref
     linkend="functions-aggregate">.  Other aggregate functions can be added
     appearing only in the result list or <literal>HAVING</> clause
     applies with respect to the query level that the aggregate belongs to.
    </para>
-
-   <note>
-    <para>
-     <productname>PostgreSQL</productname> currently does not support
-     <literal>DISTINCT</> with more than one input expression.
-    </para>
-   </note>
   </sect2>
 
   <sect2 id="syntax-window-functions">
     <literal>count(*) OVER (PARTITION BY x ORDER BY y)</>.
     <literal>*</> is customarily not used for non-aggregate window functions.
     Aggregate window functions, unlike normal aggregate functions, do not
-    allow <literal>DISTINCT</> to be used within the function argument list.
+    allow <literal>DISTINCT</> or <literal>ORDER BY</> to be used within the
+    function argument list.
    </para>
 
    <para>
 
  *       If a finalfunc is not supplied then the result is just the ending
  *       value of transvalue.
  *
+ *       If an aggregate call specifies DISTINCT or ORDER BY, we sort the input
+ *       tuples and eliminate duplicates (if required) before performing the
+ *       above-depicted process.
+ *
  *       If transfunc is marked "strict" in pg_proc and initcond is NULL,
  *       then the first non-NULL input_value is assigned directly to transvalue,
  *       and transfunc isn't applied until the second non-NULL input_value.
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/nodeAgg.c,v 1.169 2009/09/27 21:10:53 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/nodeAgg.c,v 1.170 2009/12/15 17:57:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
+#include "optimizer/tlist.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_coerce.h"
-#include "parser/parse_oper.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
        AggrefExprState *aggrefstate;
        Aggref     *aggref;
 
-       /* number of input arguments for aggregate */
+       /* number of input arguments for aggregate function proper */
        int                     numArguments;
 
+       /* number of inputs including ORDER BY expressions */
+       int                     numInputs;
+
        /* Oids of transfer functions */
        Oid                     transfn_oid;
        Oid                     finalfn_oid;    /* may be InvalidOid */
        FmgrInfo        transfn;
        FmgrInfo        finalfn;
 
-       /*
-        * Type of input data and Oid of sort operator to use for it; only
-        * set/used when aggregate has DISTINCT flag.  (These are not used
-        * directly by nodeAgg, but must be passed to the Tuplesort object.)
-        */
-       Oid                     inputType;
-       Oid                     sortOperator;
+       /* number of sorting columns */
+       int                     numSortCols;
+
+       /* number of sorting columns to consider in DISTINCT comparisons */
+       /* (this is either zero or the same as numSortCols) */
+       int         numDistinctCols;
+
+       /* deconstructed sorting information (arrays of length numSortCols) */
+       AttrNumber *sortColIdx;
+       Oid        *sortOperators;
+       bool       *sortNullsFirst;
 
        /*
-        * fmgr lookup data for input type's equality operator --- only set/used
-        * when aggregate has DISTINCT flag.
+        * fmgr lookup data for input columns' equality operators --- only
+        * set/used when aggregate has DISTINCT flag.  Note that these are in
+        * order of sort column index, not parameter index.
         */
-       FmgrInfo        equalfn;
+       FmgrInfo   *equalfns;           /* array of length numDistinctCols */
 
        /*
         * initial value from pg_aggregate entry
        /*
         * We need the len and byval info for the agg's input, result, and
         * transition data types in order to know how to copy/delete values.
+        *
+        * Note that the info for the input type is used only when handling
+        * DISTINCT aggs with just one argument, so there is only one input type.
         */
        int16           inputtypeLen,
                                resulttypeLen,
                                resulttypeByVal,
                                transtypeByVal;
 
+       /*
+        * Stuff for evaluation of inputs.  We used to just use ExecEvalExpr, but
+        * with the addition of ORDER BY we now need at least a slot for passing
+        * data to the sort object, which requires a tupledesc, so we might as
+        * well go whole hog and use ExecProject too.
+        */
+       TupleDesc   evaldesc;           /* descriptor of input tuples */
+       ProjectionInfo *evalproj;       /* projection machinery */
+
+       /*
+        * Slots for holding the evaluated input arguments.  These are set up
+        * during ExecInitAgg() and then used for each input row.
+        */
+       TupleTableSlot *evalslot;       /* current input tuple */
+       TupleTableSlot *uniqslot;       /* used for multi-column DISTINCT */
+
        /*
         * These values are working state that is initialized at the start of an
         * input tuple group and updated for each input tuple.
         *
-        * For a simple (non DISTINCT) aggregate, we just feed the input values
-        * straight to the transition function.  If it's DISTINCT, we pass the
-        * input values into a Tuplesort object; then at completion of the input
-        * tuple group, we scan the sorted values, eliminate duplicates, and run
-        * the transition function on the rest.
+        * For a simple (non DISTINCT/ORDER BY) aggregate, we just feed the input
+        * values straight to the transition function.  If it's DISTINCT or
+        * requires ORDER BY, we pass the input values into a Tuplesort object;
+        * then at completion of the input tuple group, we scan the sorted values,
+        * eliminate duplicates if needed, and run the transition function on the
+        * rest.
         */
 
-       Tuplesortstate *sortstate;      /* sort object, if a DISTINCT agg */
+       Tuplesortstate *sortstate;      /* sort object, if DISTINCT or ORDER BY */
 } AggStatePerAggData;
 
 /*
                                                        AggStatePerGroup pergroupstate,
                                                        FunctionCallInfoData *fcinfo);
 static void advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup);
-static void process_sorted_aggregate(AggState *aggstate,
+static void process_ordered_aggregate_single(AggState *aggstate,
+                                                AggStatePerAgg peraggstate,
+                                                AggStatePerGroup pergroupstate);
+static void process_ordered_aggregate_multi(AggState *aggstate,
                                                 AggStatePerAgg peraggstate,
                                                 AggStatePerGroup pergroupstate);
 static void finalize_aggregate(AggState *aggstate,
        {
                AggStatePerAgg peraggstate = &peragg[aggno];
                AggStatePerGroup pergroupstate = &pergroup[aggno];
-               Aggref     *aggref = peraggstate->aggref;
 
                /*
-                * Start a fresh sort operation for each DISTINCT aggregate.
+                * Start a fresh sort operation for each DISTINCT/ORDER BY aggregate.
                 */
-               if (aggref->aggdistinct)
+               if (peraggstate->numSortCols > 0)
                {
                        /*
                         * In case of rescan, maybe there could be an uncompleted sort
                        if (peraggstate->sortstate)
                                tuplesort_end(peraggstate->sortstate);
 
+                       /*
+                        * We use a plain Datum sorter when there's a single input
+                        * column; otherwise sort the full tuple.  (See comments for
+                        * process_ordered_aggregate_single.)
+                        */
                        peraggstate->sortstate =
-                               tuplesort_begin_datum(peraggstate->inputType,
-                                                                         peraggstate->sortOperator, false,
-                                                                         work_mem, false);
+                               (peraggstate->numInputs == 1) ?
+                               tuplesort_begin_datum(peraggstate->evaldesc->attrs[0]->atttypid,
+                                                                         peraggstate->sortOperators[0],
+                                                                         peraggstate->sortNullsFirst[0],
+                                                                         work_mem, false) :
+                               tuplesort_begin_heap(peraggstate->evaldesc,
+                                                                        peraggstate->numSortCols,
+                                                                        peraggstate->sortColIdx,
+                                                                        peraggstate->sortOperators,
+                                                                        peraggstate->sortNullsFirst,
+                                                                        work_mem, false);
                }
 
                /*
 static void
 advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
 {
-       ExprContext *econtext = aggstate->tmpcontext;
        int                     aggno;
 
        for (aggno = 0; aggno < aggstate->numaggs; aggno++)
        {
                AggStatePerAgg peraggstate = &aggstate->peragg[aggno];
                AggStatePerGroup pergroupstate = &pergroup[aggno];
-               AggrefExprState *aggrefstate = peraggstate->aggrefstate;
-               Aggref     *aggref = peraggstate->aggref;
-               FunctionCallInfoData fcinfo;
+               int                     nargs = peraggstate->numArguments;
                int                     i;
-               ListCell   *arg;
-               MemoryContext oldContext;
+               TupleTableSlot *slot;
 
-               /* Switch memory context just once for all args */
-               oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+               /* Evaluate the current input expressions for this aggregate */
+               slot = ExecProject(peraggstate->evalproj, NULL);
 
-               /* Evaluate inputs and save in fcinfo */
-               /* We start from 1, since the 0th arg will be the transition value */
-               i = 1;
-               foreach(arg, aggrefstate->args)
+               if (peraggstate->numSortCols > 0)
                {
-                       ExprState  *argstate = (ExprState *) lfirst(arg);
-
-                       fcinfo.arg[i] = ExecEvalExpr(argstate, econtext,
-                                                                                fcinfo.argnull + i, NULL);
-                       i++;
-               }
+                       /* DISTINCT and/or ORDER BY case */
+                       Assert(slot->tts_nvalid == peraggstate->numInputs);
 
-               /* Switch back */
-               MemoryContextSwitchTo(oldContext);
+                       /*
+                        * If the transfn is strict, we want to check for nullity
+                        * before storing the row in the sorter, to save space if
+                        * there are a lot of nulls.  Note that we must only check
+                        * numArguments columns, not numInputs, since nullity in
+                        * columns used only for sorting is not relevant here.
+                        */
+                       if (peraggstate->transfn.fn_strict)
+                       {
+                               for (i = 0; i < nargs; i++)
+                               {
+                                       if (slot->tts_isnull[i])
+                                               break;
+                               }
+                               if (i < nargs)
+                                       continue;
+                       }
 
-               if (aggref->aggdistinct)
-               {
-                       /* in DISTINCT mode, we may ignore nulls */
-                       /* XXX we assume there is only one input column */
-                       if (fcinfo.argnull[1])
-                               continue;
-                       tuplesort_putdatum(peraggstate->sortstate, fcinfo.arg[1],
-                                                          fcinfo.argnull[1]);
+                       /* OK, put the tuple into the tuplesort object */
+                       if (peraggstate->numInputs == 1)
+                               tuplesort_putdatum(peraggstate->sortstate,
+                                                                  slot->tts_values[0],
+                                                                  slot->tts_isnull[0]);
+                       else
+                               tuplesort_puttupleslot(peraggstate->sortstate, slot);
                }
                else
                {
+                       /* We can apply the transition function immediately */
+                       FunctionCallInfoData fcinfo;
+
+                       /* Load values into fcinfo */
+                       /* Start from 1, since the 0th arg will be the transition value */
+                       Assert(slot->tts_nvalid >= nargs);
+                       for (i = 0; i < nargs; i++)
+                       {
+                               fcinfo.arg[i + 1] = slot->tts_values[i];
+                               fcinfo.argnull[i + 1] = slot->tts_isnull[i];
+                       }
+
                        advance_transition_function(aggstate, peraggstate, pergroupstate,
                                                                                &fcinfo);
                }
        }
 }
 
+
 /*
- * Run the transition function for a DISTINCT aggregate.  This is called
- * after we have completed entering all the input values into the sort
- * object.     We complete the sort, read out the values in sorted order,
- * and run the transition function on each non-duplicate value.
+ * Run the transition function for a DISTINCT or ORDER BY aggregate
+ * with only one input.  This is called after we have completed
+ * entering all the input values into the sort object.  We complete the
+ * sort, read out the values in sorted order, and run the transition
+ * function on each value (applying DISTINCT if appropriate).
+ *
+ * Note that the strictness of the transition function was checked when
+ * entering the values into the sort, so we don't check it again here;
+ * we just apply standard SQL DISTINCT logic.
+ *
+ * The one-input case is handled separately from the multi-input case
+ * for performance reasons: for single by-value inputs, such as the
+ * common case of count(distinct id), the tuplesort_getdatum code path
+ * is around 300% faster.  (The speedup for by-reference types is less
+ * but still noticeable.)
  *
  * When called, CurrentMemoryContext should be the per-query context.
  */
 static void
-process_sorted_aggregate(AggState *aggstate,
-                                                AggStatePerAgg peraggstate,
-                                                AggStatePerGroup pergroupstate)
+process_ordered_aggregate_single(AggState *aggstate,
+                                                                AggStatePerAgg peraggstate,
+                                                                AggStatePerGroup pergroupstate)
 {
        Datum           oldVal = (Datum) 0;
+       bool        oldIsNull = true;
        bool            haveOldVal = false;
        MemoryContext workcontext = aggstate->tmpcontext->ecxt_per_tuple_memory;
        MemoryContext oldContext;
+       bool        isDistinct = (peraggstate->numDistinctCols > 0);
        Datum      *newVal;
        bool       *isNull;
        FunctionCallInfoData fcinfo;
 
+       Assert(peraggstate->numDistinctCols < 2);
+
        tuplesort_performsort(peraggstate->sortstate);
 
+       /* Load the column into argument 1 (arg 0 will be transition value) */
        newVal = fcinfo.arg + 1;
        isNull = fcinfo.argnull + 1;
 
        while (tuplesort_getdatum(peraggstate->sortstate, true,
                                                          newVal, isNull))
        {
-               /*
-                * DISTINCT always suppresses nulls, per SQL spec, regardless of the
-                * transition function's strictness.
-                */
-               if (*isNull)
-                       continue;
-
                /*
                 * Clear and select the working context for evaluation of the equality
                 * function and transition function.
                MemoryContextReset(workcontext);
                oldContext = MemoryContextSwitchTo(workcontext);
 
-               if (haveOldVal &&
-                       DatumGetBool(FunctionCall2(&peraggstate->equalfn,
-                                                                          oldVal, *newVal)))
+               /*
+                * If DISTINCT mode, and not distinct from prior, skip it.
+                */
+               if (isDistinct &&
+                       haveOldVal &&
+                       ((oldIsNull && *isNull) ||
+                        (!oldIsNull && !*isNull &&
+                         DatumGetBool(FunctionCall2(&peraggstate->equalfns[0],
+                                                                                oldVal, *newVal)))))
                {
                        /* equal to prior, so forget this one */
-                       if (!peraggstate->inputtypeByVal)
+                       if (!peraggstate->inputtypeByVal && !*isNull)
                                pfree(DatumGetPointer(*newVal));
                }
                else
                        advance_transition_function(aggstate, peraggstate, pergroupstate,
                                                                                &fcinfo);
                        /* forget the old value, if any */
-                       if (haveOldVal && !peraggstate->inputtypeByVal)
+                       if (!oldIsNull && !peraggstate->inputtypeByVal)
                                pfree(DatumGetPointer(oldVal));
                        /* and remember the new one for subsequent equality checks */
                        oldVal = *newVal;
+                       oldIsNull = *isNull;
                        haveOldVal = true;
                }
 
                MemoryContextSwitchTo(oldContext);
        }
 
-       if (haveOldVal && !peraggstate->inputtypeByVal)
+       if (!oldIsNull && !peraggstate->inputtypeByVal)
                pfree(DatumGetPointer(oldVal));
 
        tuplesort_end(peraggstate->sortstate);
        peraggstate->sortstate = NULL;
 }
 
+/*
+ * Run the transition function for a DISTINCT or ORDER BY aggregate
+ * with more than one input.  This is called after we have completed
+ * entering all the input values into the sort object.  We complete the
+ * sort, read out the values in sorted order, and run the transition
+ * function on each value (applying DISTINCT if appropriate).
+ *
+ * When called, CurrentMemoryContext should be the per-query context.
+ */
+static void
+process_ordered_aggregate_multi(AggState *aggstate,
+                                                               AggStatePerAgg peraggstate,
+                                                               AggStatePerGroup pergroupstate)
+{
+       MemoryContext workcontext = aggstate->tmpcontext->ecxt_per_tuple_memory;
+       FunctionCallInfoData fcinfo;
+       TupleTableSlot *slot1 = peraggstate->evalslot;
+       TupleTableSlot *slot2 = peraggstate->uniqslot;
+       int         numArguments = peraggstate->numArguments;
+       int         numDistinctCols = peraggstate->numDistinctCols;
+       bool        haveOldValue = false;
+       int         i;
+
+       tuplesort_performsort(peraggstate->sortstate);
+
+       ExecClearTuple(slot1);
+       if (slot2)
+               ExecClearTuple(slot2);
+
+       while (tuplesort_gettupleslot(peraggstate->sortstate, true, slot1))
+       {
+               /*
+                * Extract the first numArguments as datums to pass to the transfn.
+                * (This will help execTuplesMatch too, so do it immediately.)
+                */
+               slot_getsomeattrs(slot1, numArguments);
+
+               if (numDistinctCols == 0 ||
+                       !haveOldValue ||
+                       !execTuplesMatch(slot1, slot2,
+                                                        numDistinctCols,
+                                                        peraggstate->sortColIdx,
+                                                        peraggstate->equalfns,
+                                                        workcontext))
+               {
+                       /* Load values into fcinfo */
+                       /* Start from 1, since the 0th arg will be the transition value */
+                       for (i = 0; i < numArguments; i++)
+                       {
+                               fcinfo.arg[i + 1] = slot1->tts_values[i];
+                               fcinfo.argnull[i + 1] = slot1->tts_isnull[i];
+                       }
+
+                       advance_transition_function(aggstate, peraggstate, pergroupstate,
+                                                                               &fcinfo);
+
+                       if (numDistinctCols > 0)
+                       {
+                               /* swap the slot pointers to retain the current tuple */
+                               TupleTableSlot *tmpslot = slot2;
+
+                               slot2 = slot1;
+                               slot1 = tmpslot;
+                               haveOldValue = true;
+                       }
+               }
+
+               /* Reset context each time, unless execTuplesMatch did it for us */
+               if (numDistinctCols == 0)
+                       MemoryContextReset(workcontext);
+
+               ExecClearTuple(slot1);
+       }
+
+       if (slot2)
+               ExecClearTuple(slot2);
+
+       tuplesort_end(peraggstate->sortstate);
+       peraggstate->sortstate = NULL;
+}
+
 /*
  * Compute the final value of one aggregate.
  *
                        AggStatePerAgg peraggstate = &peragg[aggno];
                        AggStatePerGroup pergroupstate = &pergroup[aggno];
 
-                       if (peraggstate->aggref->aggdistinct)
-                               process_sorted_aggregate(aggstate, peraggstate, pergroupstate);
+                       if (peraggstate->numSortCols > 0)
+                       {
+                               if (peraggstate->numInputs == 1)
+                                       process_ordered_aggregate_single(aggstate,
+                                                                                                        peraggstate,
+                                                                                                        pergroupstate);
+                               else
+                                       process_ordered_aggregate_multi(aggstate,
+                                                                                                       peraggstate,
+                                                                                                       pergroupstate);
+                       }
 
                        finalize_aggregate(aggstate, peraggstate, pergroupstate,
                                                           &aggvalues[aggno], &aggnulls[aggno]);
                        AggStatePerAgg peraggstate = &peragg[aggno];
                        AggStatePerGroup pergroupstate = &pergroup[aggno];
 
-                       Assert(!peraggstate->aggref->aggdistinct);
+                       Assert(peraggstate->numSortCols == 0);
                        finalize_aggregate(aggstate, peraggstate, pergroupstate,
                                                           &aggvalues[aggno], &aggnulls[aggno]);
                }
                AggStatePerAgg peraggstate;
                Oid                     inputTypes[FUNC_MAX_ARGS];
                int                     numArguments;
+               int                     numInputs;
+               int                     numSortCols;
+               int                     numDistinctCols;
+               List       *sortlist;
                HeapTuple       aggTuple;
                Form_pg_aggregate aggform;
                Oid                     aggtranstype;
                /* Fill in the peraggstate data */
                peraggstate->aggrefstate = aggrefstate;
                peraggstate->aggref = aggref;
-               numArguments = list_length(aggref->args);
-               peraggstate->numArguments = numArguments;
+               numInputs = list_length(aggref->args);
+               peraggstate->numInputs = numInputs;
+               peraggstate->sortstate = NULL;
 
                /*
                 * Get actual datatypes of the inputs.  These could be different from
                 * the agg's declared input types, when the agg accepts ANY or a
                 * polymorphic type.
                 */
-               i = 0;
+               numArguments = 0;
                foreach(lc, aggref->args)
                {
-                       inputTypes[i++] = exprType((Node *) lfirst(lc));
+                       TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+                       if (!tle->resjunk)
+                               inputTypes[numArguments++] = exprType((Node *) tle->expr);
                }
+               peraggstate->numArguments = numArguments;
 
                aggTuple = SearchSysCache(AGGFNOID,
                                                                  ObjectIdGetDatum(aggref->aggfnoid),
                                                                aggref->aggfnoid)));
                }
 
+               /*
+                * Get a tupledesc corresponding to the inputs (including sort
+                * expressions) of the agg.
+                */
+               peraggstate->evaldesc = ExecTypeFromTL(aggref->args, false);
+
+               /* Create slot we're going to do argument evaluation in */
+               peraggstate->evalslot = ExecInitExtraTupleSlot(estate);
+               ExecSetSlotDescriptor(peraggstate->evalslot, peraggstate->evaldesc);
+
+               /* Set up projection info for evaluation */
+               peraggstate->evalproj = ExecBuildProjectionInfo(aggrefstate->args,
+                                                                                                               aggstate->tmpcontext,
+                                                                                                               peraggstate->evalslot,
+                                                                                                               NULL);
+
+               /*
+                * If we're doing either DISTINCT or ORDER BY, then we have a list
+                * of SortGroupClause nodes; fish out the data in them and
+                * stick them into arrays.
+                *
+                * Note that by construction, if there is a DISTINCT clause then the
+                * ORDER BY clause is a prefix of it (see transformDistinctClause).
+                */
                if (aggref->aggdistinct)
                {
-                       Oid                     lt_opr;
-                       Oid                     eq_opr;
+                       sortlist = aggref->aggdistinct;
+                       numSortCols = numDistinctCols = list_length(sortlist);
+                       Assert(numSortCols >= list_length(aggref->aggorder));
+               }
+               else
+               {
+                       sortlist = aggref->aggorder;
+                       numSortCols = list_length(sortlist);
+                       numDistinctCols = 0;
+               }
 
-                       /* We don't implement DISTINCT aggs in the HASHED case */
-                       Assert(node->aggstrategy != AGG_HASHED);
+               peraggstate->numSortCols = numSortCols;
+               peraggstate->numDistinctCols = numDistinctCols;
 
+               if (numSortCols > 0)
+               {
                        /*
-                        * We don't currently implement DISTINCT aggs for aggs having more
-                        * than one argument.  This isn't required for anything in the SQL
-                        * spec, but really it ought to be implemented for
-                        * feature-completeness.  FIXME someday.
+                        * We don't implement DISTINCT or ORDER BY aggs in the HASHED case
+                        * (yet)
                         */
-                       if (numArguments != 1)
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                errmsg("DISTINCT is supported only for single-argument aggregates")));
+                       Assert(node->aggstrategy != AGG_HASHED);
+
+                       /* If we have only one input, we need its len/byval info. */
+                       if (numInputs == 1)
+                       {
+                               get_typlenbyval(inputTypes[0],
+                                                               &peraggstate->inputtypeLen,
+                                                               &peraggstate->inputtypeByVal);
+                       }
+                       else if (numDistinctCols > 0)
+                       {
+                               /* we will need an extra slot to store prior values */
+                               peraggstate->uniqslot = ExecInitExtraTupleSlot(estate);
+                               ExecSetSlotDescriptor(peraggstate->uniqslot,
+                                                                         peraggstate->evaldesc);
+                       }
+
+                       /* Extract the sort information for use later */
+                       peraggstate->sortColIdx =
+                               (AttrNumber *) palloc(numSortCols * sizeof(AttrNumber));
+                       peraggstate->sortOperators =
+                               (Oid *) palloc(numSortCols * sizeof(Oid));
+                       peraggstate->sortNullsFirst =
+                               (bool *) palloc(numSortCols * sizeof(bool));
+
+                       i = 0;
+                       foreach(lc, sortlist)
+                       {
+                               SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
+                               TargetEntry *tle = get_sortgroupclause_tle(sortcl,
+                                                                                                                  aggref->args);
+
+                               /* the parser should have made sure of this */
+                               Assert(OidIsValid(sortcl->sortop));
 
-                       peraggstate->inputType = inputTypes[0];
-                       get_typlenbyval(inputTypes[0],
-                                                       &peraggstate->inputtypeLen,
-                                                       &peraggstate->inputtypeByVal);
+                               peraggstate->sortColIdx[i] = tle->resno;
+                               peraggstate->sortOperators[i] = sortcl->sortop;
+                               peraggstate->sortNullsFirst[i] = sortcl->nulls_first;
+                               i++;
+                       }
+                       Assert(i == numSortCols);
+               }
+
+               if (aggref->aggdistinct)
+               {
+                       Assert(numArguments > 0);
 
                        /*
-                        * Look up the sorting and comparison operators to use.  XXX it's
-                        * pretty bletcherous to be making this sort of semantic decision
-                        * in the executor.  Probably the parser should decide this and
-                        * record it in the Aggref node ... or at latest, do it in the
-                        * planner.
+                        * We need the equal function for each DISTINCT comparison we will
+                        * make.
                         */
-                       get_sort_group_operators(inputTypes[0],
-                                                                        true, true, false,
-                                                                        <_opr, &eq_opr, NULL);
-                       fmgr_info(get_opcode(eq_opr), &(peraggstate->equalfn));
-                       peraggstate->sortOperator = lt_opr;
-                       peraggstate->sortstate = NULL;
+                       peraggstate->equalfns =
+                               (FmgrInfo *) palloc(numDistinctCols * sizeof(FmgrInfo));
+
+                       i = 0;
+                       foreach(lc, aggref->aggdistinct)
+                       {
+                               SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
+
+                               fmgr_info(get_opcode(sortcl->eqop), &peraggstate->equalfns[i]);
+                               i++;
+                       }
+                       Assert(i == numDistinctCols);
                }
 
                ReleaseSysCache(aggTuple);
 
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.453 2009/12/07 05:22:22 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.454 2009/12/15 17:57:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
        COPY_SCALAR_FIELD(aggfnoid);
        COPY_SCALAR_FIELD(aggtype);
        COPY_NODE_FIELD(args);
-       COPY_SCALAR_FIELD(agglevelsup);
+       COPY_NODE_FIELD(aggorder);
+       COPY_NODE_FIELD(aggdistinct);
        COPY_SCALAR_FIELD(aggstar);
-       COPY_SCALAR_FIELD(aggdistinct);
+       COPY_SCALAR_FIELD(agglevelsup);
        COPY_LOCATION_FIELD(location);
 
        return newnode;
 
        COPY_NODE_FIELD(funcname);
        COPY_NODE_FIELD(args);
+       COPY_NODE_FIELD(agg_order);
        COPY_SCALAR_FIELD(agg_star);
        COPY_SCALAR_FIELD(agg_distinct);
        COPY_SCALAR_FIELD(func_variadic);
 
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.375 2009/12/07 05:22:22 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.376 2009/12/15 17:57:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
        COMPARE_SCALAR_FIELD(aggfnoid);
        COMPARE_SCALAR_FIELD(aggtype);
        COMPARE_NODE_FIELD(args);
-       COMPARE_SCALAR_FIELD(agglevelsup);
+       COMPARE_NODE_FIELD(aggorder);
+       COMPARE_NODE_FIELD(aggdistinct);
        COMPARE_SCALAR_FIELD(aggstar);
-       COMPARE_SCALAR_FIELD(aggdistinct);
+       COMPARE_SCALAR_FIELD(agglevelsup);
        COMPARE_LOCATION_FIELD(location);
 
        return true;
 {
        COMPARE_NODE_FIELD(funcname);
        COMPARE_NODE_FIELD(args);
+       COMPARE_NODE_FIELD(agg_order);
        COMPARE_SCALAR_FIELD(agg_star);
        COMPARE_SCALAR_FIELD(agg_distinct);
        COMPARE_SCALAR_FIELD(func_variadic);
 
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/nodeFuncs.c,v 1.43 2009/10/08 02:39:21 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/nodeFuncs.c,v 1.44 2009/12/15 17:57:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
                                FuncCall   *fc = (FuncCall *) expr;
 
                                /* consider both function name and leftmost arg */
+                               /* (we assume any ORDER BY nodes must be to right of name) */
                                loc = leftmostLoc(fc->location,
                                                                  exprLocation((Node *) fc->args));
                        }
                case T_SetToDefault:
                case T_CurrentOfExpr:
                case T_RangeTblRef:
+               case T_SortGroupClause:
                        /* primitive node types with no expression subnodes */
                        break;
                case T_Aggref:
                                if (expression_tree_walker((Node *) expr->args,
                                                                                   walker, context))
                                        return true;
+                               if (expression_tree_walker((Node *) expr->aggorder,
+                                                                                  walker, context))
+                                       return true;
+                               if (expression_tree_walker((Node *) expr->aggdistinct,
+                                                                                  walker, context))
+                                       return true;
                        }
                        break;
                case T_WindowFunc:
                case T_SetToDefault:
                case T_CurrentOfExpr:
                case T_RangeTblRef:
+               case T_SortGroupClause:
                        return (Node *) copyObject(node);
                case T_Aggref:
                        {
 
                                FLATCOPY(newnode, aggref, Aggref);
                                MUTATE(newnode->args, aggref->args, List *);
+                               MUTATE(newnode->aggorder, aggref->aggorder, List *);
+                               MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
                                return (Node *) newnode;
                        }
                        break;
 
                                if (walker(fcall->args, context))
                                        return true;
+                               if (walker(fcall->agg_order, context))
+                                       return true;
                                if (walker(fcall->over, context))
                                        return true;
                                /* function name is deemed uninteresting */
 
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.374 2009/12/07 05:22:22 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.375 2009/12/15 17:57:46 tgl Exp $
  *
  * NOTES
  *       Every node type that can appear in stored rules' parsetrees *must*
        WRITE_OID_FIELD(aggfnoid);
        WRITE_OID_FIELD(aggtype);
        WRITE_NODE_FIELD(args);
-       WRITE_UINT_FIELD(agglevelsup);
+       WRITE_NODE_FIELD(aggorder);
+       WRITE_NODE_FIELD(aggdistinct);
        WRITE_BOOL_FIELD(aggstar);
-       WRITE_BOOL_FIELD(aggdistinct);
+       WRITE_UINT_FIELD(agglevelsup);
        WRITE_LOCATION_FIELD(location);
 }
 
 
        WRITE_NODE_FIELD(funcname);
        WRITE_NODE_FIELD(args);
+       WRITE_NODE_FIELD(agg_order);
        WRITE_BOOL_FIELD(agg_star);
        WRITE_BOOL_FIELD(agg_distinct);
        WRITE_BOOL_FIELD(func_variadic);
 
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.227 2009/10/28 14:55:38 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.228 2009/12/15 17:57:46 tgl Exp $
  *
  * NOTES
  *       Path and Plan nodes do not have any readfuncs support, because we
        READ_OID_FIELD(aggfnoid);
        READ_OID_FIELD(aggtype);
        READ_NODE_FIELD(args);
-       READ_UINT_FIELD(agglevelsup);
+       READ_NODE_FIELD(aggorder);
+       READ_NODE_FIELD(aggdistinct);
        READ_BOOL_FIELD(aggstar);
-       READ_BOOL_FIELD(aggdistinct);
+       READ_UINT_FIELD(agglevelsup);
        READ_LOCATION_FIELD(location);
 
        READ_DONE();
 
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.46 2009/06/11 14:48:59 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.47 2009/12/15 17:57:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
        {
                Aggref     *aggref = (Aggref *) node;
                Oid                     aggsortop;
-               Expr       *curTarget;
+               TargetEntry *curTarget;
                MinMaxAggInfo *info;
                ListCell   *l;
 
                Assert(aggref->agglevelsup == 0);
-               if (list_length(aggref->args) != 1)
+               if (list_length(aggref->args) != 1 || aggref->aggorder != NIL)
                        return true;            /* it couldn't be MIN/MAX */
                /* note: we do not care if DISTINCT is mentioned ... */
 
                /*
                 * Check whether it's already in the list, and add it if not.
                 */
-               curTarget = linitial(aggref->args);
+               curTarget = (TargetEntry *) linitial(aggref->args);
                foreach(l, *context)
                {
                        info = (MinMaxAggInfo *) lfirst(l);
                        if (info->aggfnoid == aggref->aggfnoid &&
-                               equal(info->target, curTarget))
+                               equal(info->target, curTarget->expr))
                                return false;
                }
 
                info = (MinMaxAggInfo *) palloc0(sizeof(MinMaxAggInfo));
                info->aggfnoid = aggref->aggfnoid;
                info->aggsortop = aggsortop;
-               info->target = curTarget;
+               info->target = curTarget->expr;
 
                *context = lappend(*context, info);
 
        if (IsA(node, Aggref))
        {
                Aggref     *aggref = (Aggref *) node;
+               TargetEntry *curTarget = (TargetEntry *) linitial(aggref->args);
                ListCell   *l;
-               Expr       *curTarget = linitial(aggref->args);
 
                foreach(l, *context)
                {
                        MinMaxAggInfo *info = (MinMaxAggInfo *) lfirst(l);
 
                        if (info->aggfnoid == aggref->aggfnoid &&
-                               equal(info->target, curTarget))
+                               equal(info->target, curTarget->expr))
                                return (Node *) info->param;
                }
                elog(ERROR, "failed to re-find aggregate info record");
 
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.261 2009/10/28 14:55:38 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.262 2009/12/15 17:57:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
                        bool            can_sort;
 
                        /*
-                        * Executor doesn't support hashed aggregation with DISTINCT
-                        * aggregates.  (Doing so would imply storing *all* the input
-                        * values in the hash table, which seems like a certain loser.)
+                        * Executor doesn't support hashed aggregation with DISTINCT or
+                        * ORDER BY aggregates.  (Doing so would imply storing *all* the
+                        * input values in the hash table, and/or running many sorts in
+                        * parallel, either of which seems like a certain loser.)
                         */
-                       can_hash = (agg_counts.numDistinctAggs == 0 &&
+                       can_hash = (agg_counts.numOrderedAggs == 0 &&
                                                grouping_is_hashable(parse->groupClause));
                        can_sort = grouping_is_sortable(parse->groupClause);
                        if (can_hash && can_sort)
 
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.280 2009/12/14 02:15:52 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.281 2009/12/15 17:57:47 tgl Exp $
  *
  * HISTORY
  *       AUTHOR                        DATE                    MAJOR EVENT
                HeapTuple       aggTuple;
                Form_pg_aggregate aggform;
                Oid                     aggtranstype;
-               int                     i;
                ListCell   *l;
 
                Assert(aggref->agglevelsup == 0);
                counts->numAggs++;
-               if (aggref->aggdistinct)
-                       counts->numDistinctAggs++;
+               if (aggref->aggorder != NIL || aggref->aggdistinct != NIL)
+                       counts->numOrderedAggs++;
 
-               /* extract argument types */
-               numArguments = list_length(aggref->args);
-               inputTypes = (Oid *) palloc(sizeof(Oid) * numArguments);
-               i = 0;
+               /* extract argument types (ignoring any ORDER BY expressions) */
+               inputTypes = (Oid *) palloc(sizeof(Oid) * list_length(aggref->args));
+               numArguments = 0;
                foreach(l, aggref->args)
                {
-                       inputTypes[i++] = exprType((Node *) lfirst(l));
+                       TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+                       if (!tle->resjunk)
+                               inputTypes[numArguments++] = exprType((Node *) tle->expr);
                }
 
                /* fetch aggregate transition datatype from pg_aggregate */
 
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *     $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.396 2009/10/31 01:41:31 tgl Exp $
+ *     $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.397 2009/12/15 17:57:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
                                                                                  stmt->sortClause,
                                                                                  &qry->targetList,
                                                                                  true /* fix unknowns */,
-                                                                                 false /* not window function */);
+                                                                                 false /* allow SQL92 rules */);
 
        qry->groupClause = transformGroupClause(pstate,
                                                                                        stmt->groupClause,
                                                                                        &qry->targetList,
                                                                                        qry->sortClause,
-                                                                                       false /* not window function */);
+                                                                                       false /* allow SQL92 rules */);
 
        if (stmt->distinctClause == NIL)
        {
                /* We had SELECT DISTINCT */
                qry->distinctClause = transformDistinctClause(pstate,
                                                                                                          &qry->targetList,
-                                                                                                         qry->sortClause);
+                                                                                                         qry->sortClause,
+                                                                                                         false);
                qry->hasDistinctOn = false;
        }
        else
                                                                                  stmt->sortClause,
                                                                                  &qry->targetList,
                                                                                  true /* fix unknowns */,
-                                                                                 false /* not window function */);
+                                                                                 false /* allow SQL92 rules */);
 
        qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
                                                                                        "OFFSET");
                                                                                  sortClause,
                                                                                  &qry->targetList,
                                                                                  false /* no unknowns expected */,
-                                                                                 false /* not window function */);
+                                                                                 false /* allow SQL92 rules */);
 
        pstate->p_rtable = list_truncate(pstate->p_rtable, sv_rtable_length);
        pstate->p_relnamespace = sv_relnamespace;
 
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.696 2009/12/11 03:34:55 itagaki Exp $
+ *       $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.697 2009/12/15 17:57:47 tgl Exp $
  *
  * HISTORY
  *       AUTHOR                        DATE                    MAJOR EVENT
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("timezone");
                                        n->args = list_make2($5, $1);
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("like_escape");
                                        n->args = list_make2($3, $5);
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("like_escape");
                                        n->args = list_make2($4, $6);
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("like_escape");
                                        n->args = list_make2($3, $5);
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("like_escape");
                                        n->args = list_make2($4, $6);
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("similar_escape");
                                        n->args = list_make2($4, makeNullAConst(-1));
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("similar_escape");
                                        n->args = list_make2($4, $6);
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("similar_escape");
                                        n->args = list_make2($5, makeNullAConst(-1));
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("similar_escape");
                                        n->args = list_make2($5, $7);
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = $1;
                                        n->args = NIL;
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = $1;
                                        n->args = $3;
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = $1;
                                        n->args = list_make1($4);
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = TRUE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = $1;
                                        n->args = lappend($3, $6);
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = TRUE;
                                        n->location = @1;
                                        $$ = (Node *)n;
                                }
-                       | func_name '(' ALL func_arg_list ')' over_clause
+                       | func_name '(' func_arg_list sort_clause ')' over_clause
+                               {
+                                       FuncCall *n = makeNode(FuncCall);
+                                       n->funcname = $1;
+                                       n->args = $3;
+                                       n->agg_order = $4;
+                                       n->agg_star = FALSE;
+                                       n->agg_distinct = FALSE;
+                                       n->func_variadic = FALSE;
+                                       n->over = $6;
+                                       n->location = @1;
+                                       $$ = (Node *)n;
+                               }
+                       | func_name '(' ALL func_arg_list opt_sort_clause ')' over_clause
                                {
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = $1;
                                        n->args = $4;
+                                       n->agg_order = $5;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        /* Ideally we'd mark the FuncCall node to indicate
                                         * for that in FuncCall at the moment.
                                         */
                                        n->func_variadic = FALSE;
-                                       n->over = $6;
+                                       n->over = $7;
                                        n->location = @1;
                                        $$ = (Node *)n;
                                }
-                       | func_name '(' DISTINCT func_arg_list ')' over_clause
+                       | func_name '(' DISTINCT func_arg_list opt_sort_clause ')' over_clause
                                {
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = $1;
                                        n->args = $4;
+                                       n->agg_order = $5;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = TRUE;
                                        n->func_variadic = FALSE;
-                                       n->over = $6;
+                                       n->over = $7;
                                        n->location = @1;
                                        $$ = (Node *)n;
                                }
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = $1;
                                        n->args = NIL;
+                                       n->agg_order = NIL;
                                        n->agg_star = TRUE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("now");
                                        n->args = NIL;
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("current_user");
                                        n->args = NIL;
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("current_user");
                                        n->args = NIL;
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("session_user");
                                        n->args = NIL;
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("current_user");
                                        n->args = NIL;
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("current_database");
                                        n->args = NIL;
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("current_schema");
                                        n->args = NIL;
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("date_part");
                                        n->args = $3;
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("overlay");
                                        n->args = $3;
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("position");
                                        n->args = $3;
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("substring");
                                        n->args = $3;
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                         */
                                        n->funcname = SystemFuncName(((Value *)llast($5->names))->val.str);
                                        n->args = list_make1($3);
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("btrim");
                                        n->args = $4;
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("ltrim");
                                        n->args = $4;
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("rtrim");
                                        n->args = $4;
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                        FuncCall *n = makeNode(FuncCall);
                                        n->funcname = SystemFuncName("btrim");
                                        n->args = $3;
+                                       n->agg_order = NIL;
                                        n->agg_star = FALSE;
                                        n->agg_distinct = FALSE;
                                        n->func_variadic = FALSE;
                                 errmsg("wrong number of parameters on right side of OVERLAPS expression"),
                                 parser_errposition(location)));
        n->args = list_concat(largs, rargs);
+       n->agg_order = NIL;
        n->agg_star = FALSE;
        n->agg_distinct = FALSE;
        n->func_variadic = FALSE;
 
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/parser/parse_agg.c,v 1.88 2009/06/11 14:49:00 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/parser/parse_agg.c,v 1.89 2009/12/15 17:57:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "optimizer/tlist.h"
 #include "optimizer/var.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
+#include "utils/builtins.h"
 #include "utils/lsyscache.h"
 
 
  *             Finish initial transformation of an aggregate call
  *
  * parse_func.c has recognized the function as an aggregate, and has set
- * up all the fields of the Aggref except agglevelsup. Here we must
- * determine which query level the aggregate actually belongs to, set
- * agglevelsup accordingly, and mark p_hasAggs true in the corresponding
+ * up all the fields of the Aggref except aggdistinct and agglevelsup.
+ * However, the args list is just bare expressions, and the aggorder list
+ * hasn't been transformed at all.
+ *
+ * Here we convert the args list into a targetlist by inserting TargetEntry
+ * nodes, and then transform the aggorder and agg_distinct specifications to
+ * produce lists of SortGroupClause nodes.  (That might also result in adding
+ * resjunk expressions to the targetlist.)
+ *
+ * We must also determine which query level the aggregate actually belongs to,
+ * set agglevelsup accordingly, and mark p_hasAggs true in the corresponding
  * pstate level.
  */
 void
-transformAggregateCall(ParseState *pstate, Aggref *agg)
+transformAggregateCall(ParseState *pstate, Aggref *agg, bool agg_distinct)
 {
+       List       *tlist;
+       List       *torder;
+       List       *tdistinct = NIL;
+       AttrNumber  attno;
+       int         save_next_resno;
        int                     min_varlevel;
+       ListCell   *lc;
+
+       /*
+        * Transform the plain list of Exprs into a targetlist.  We don't bother
+        * to assign column names to the entries.
+        */
+       tlist = NIL;
+       attno = 1;
+       foreach(lc, agg->args)
+       {
+               Expr        *arg  = (Expr *) lfirst(lc);
+               TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false);
+
+               tlist = lappend(tlist, tle);
+       }
+
+       /*
+        * If we have an ORDER BY, transform it.  This will add columns to the
+        * tlist if they appear in ORDER BY but weren't already in the arg list.
+        * They will be marked resjunk = true so we can tell them apart from
+        * regular aggregate arguments later.
+        *
+        * We need to mess with p_next_resno since it will be used to number any
+        * new targetlist entries.
+        */
+       save_next_resno = pstate->p_next_resno;
+       pstate->p_next_resno = attno;
+
+       torder = transformSortClause(pstate,
+                                                                agg->aggorder,
+                                                                &tlist,
+                                                                true /* fix unknowns */,
+                                                                true /* force SQL99 rules */);
+
+       /*
+        * If we have DISTINCT, transform that to produce a distinctList.
+        */
+       if (agg_distinct)
+       {
+               tdistinct = transformDistinctClause(pstate, &tlist, torder, true);
+
+               /*
+                * Remove this check if executor support for hashed distinct for
+                * aggregates is ever added.
+                */
+               foreach(lc, tdistinct)
+               {
+                       SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
+
+                       if (!OidIsValid(sortcl->sortop))
+                       {
+                               Node   *expr = get_sortgroupclause_expr(sortcl, tlist);
+
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_UNDEFINED_FUNCTION),
+                                                errmsg("could not identify an ordering operator for type %s",
+                                                               format_type_be(exprType(expr))),
+                                                errdetail("Aggregates with DISTINCT must be able to sort their inputs."),
+                                                parser_errposition(pstate, exprLocation(expr))));
+                       }
+               }
+       }
+
+       /* Update the Aggref with the transformation results */
+       agg->args = tlist;
+       agg->aggorder = torder;
+       agg->aggdistinct = tdistinct;
+
+       pstate->p_next_resno = save_next_resno;
 
        /*
         * The aggregate's level is the same as the level of the lowest-level
 
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.193 2009/10/27 17:11:18 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.194 2009/12/15 17:57:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 List *
 transformGroupClause(ParseState *pstate, List *grouplist,
                                         List **targetlist, List *sortClause,
-                                        bool isWindowFunc)
+                                        bool useSQL99)
 {
        List       *result = NIL;
        ListCell   *gl;
                TargetEntry *tle;
                bool            found = false;
 
-               if (isWindowFunc)
+               if (useSQL99)
                        tle = findTargetlistEntrySQL99(pstate, gexpr, targetlist);
                else
                        tle = findTargetlistEntrySQL92(pstate, gexpr, targetlist,
  * ORDER BY items will be added to the targetlist (as resjunk columns)
  * if not already present, so the targetlist must be passed by reference.
  *
- * This is also used for window ORDER BY clauses (which act almost the
- * same, but are always interpreted per SQL99 rules).
+ * This is also used for window and aggregate ORDER BY clauses (which act
+ * almost the same, but are always interpreted per SQL99 rules).
  */
 List *
 transformSortClause(ParseState *pstate,
                                        List *orderlist,
                                        List **targetlist,
                                        bool resolveUnknown,
-                                       bool isWindowFunc)
+                                       bool useSQL99)
 {
        List       *sortlist = NIL;
        ListCell   *olitem;
                SortBy     *sortby = (SortBy *) lfirst(olitem);
                TargetEntry *tle;
 
-               if (isWindowFunc)
+               if (useSQL99)
                        tle = findTargetlistEntrySQL99(pstate, sortby->node, targetlist);
                else
                        tle = findTargetlistEntrySQL92(pstate, sortby->node, targetlist,
                                                                                  windef->orderClause,
                                                                                  targetlist,
                                                                                  true /* fix unknowns */,
-                                                                                 true /* window function */);
+                                                                                 true /* force SQL99 rules */);
                partitionClause = transformGroupClause(pstate,
                                                                                           windef->partitionClause,
                                                                                           targetlist,
                                                                                           orderClause,
-                                                                                          true /* window function */);
+                                                                                          true /* force SQL99 rules */);
 
                /*
                 * And prepare the new WindowClause.
  * and allows the user to choose the equality semantics used by DISTINCT,
  * should she be working with a datatype that has more than one equality
  * operator.
+ *
+ * is_agg is true if we are transforming an aggregate(DISTINCT ...)
+ * function call.  This does not affect any behavior, only the phrasing
+ * of error messages.
  */
 List *
 transformDistinctClause(ParseState *pstate,
-                                               List **targetlist, List *sortClause)
+                                               List **targetlist, List *sortClause, bool is_agg)
 {
        List       *result = NIL;
        ListCell   *slitem;
                if (tle->resjunk)
                        ereport(ERROR,
                                        (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+                                        is_agg ?
+                                        errmsg("in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list") :
                                         errmsg("for SELECT DISTINCT, ORDER BY expressions must appear in select list"),
                                         parser_errposition(pstate,
                                                                                exprLocation((Node *) tle->expr))));
 
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.250 2009/11/13 19:48:20 heikki Exp $
+ *       $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.251 2009/12/15 17:57:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
                        newresult = ParseFuncOrColumn(pstate,
                                                                                  list_make1(n),
                                                                                  list_make1(result),
-                                                                                 false, false, false,
+                                                                                 NIL, false, false, false,
                                                                                  NULL, true, location);
                        if (newresult == NULL)
                                unknown_attribute(pstate, result, strVal(n), location);
                                        node = ParseFuncOrColumn(pstate,
                                                                                         list_make1(makeString(colname)),
                                                                                         list_make1(node),
-                                                                                        false, false, false,
+                                                                                        NIL, false, false, false,
                                                                                         NULL, true, cref->location);
                                }
                                break;
                                        node = ParseFuncOrColumn(pstate,
                                                                                         list_make1(makeString(colname)),
                                                                                         list_make1(node),
-                                                                                        false, false, false,
+                                                                                        NIL, false, false, false,
                                                                                         NULL, true, cref->location);
                                }
                                break;
                                        node = ParseFuncOrColumn(pstate,
                                                                                         list_make1(makeString(colname)),
                                                                                         list_make1(node),
-                                                                                        false, false, false,
+                                                                                        NIL, false, false, false,
                                                                                         NULL, true, cref->location);
                                }
                                break;
        return ParseFuncOrColumn(pstate,
                                                         fn->funcname,
                                                         targs,
+                                                        fn->agg_order,
                                                         fn->agg_star,
                                                         fn->agg_distinct,
                                                         fn->func_variadic,
 
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.218 2009/10/31 01:41:31 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.219 2009/12/15 17:57:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
  *     reporting a no-such-function error.
  *
  *     The argument expressions (in fargs) must have been transformed already.
+ *     But the agg_order expressions, if any, have not been.
  */
 Node *
 ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
-                                 bool agg_star, bool agg_distinct, bool func_variadic,
+                                 List *agg_order, bool agg_star, bool agg_distinct,
+                                 bool func_variadic,
                                  WindowDef *over, bool is_column, int location)
 {
        Oid                     rettype;
         * the "function call" could be a projection.  We also check that there
         * wasn't any aggregate or variadic decoration, nor an argument name.
         */
-       if (nargs == 1 && !agg_star && !agg_distinct && over == NULL &&
-               !func_variadic && argnames == NIL && list_length(funcname) == 1)
+       if (nargs == 1 && agg_order == NIL && !agg_star && !agg_distinct &&
+               over == NULL && !func_variadic && argnames == NIL &&
+               list_length(funcname) == 1)
        {
                Oid                     argtype = actual_arg_types[0];
 
                        errmsg("DISTINCT specified, but %s is not an aggregate function",
                                   NameListToString(funcname)),
                                         parser_errposition(pstate, location)));
+               if (agg_order != NIL)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                          errmsg("ORDER BY specified, but %s is not an aggregate function",
+                                         NameListToString(funcname)),
+                                        parser_errposition(pstate, location)));
                if (over)
                        ereport(ERROR,
                                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
 
                aggref->aggfnoid = funcid;
                aggref->aggtype = rettype;
+               /* args and aggorder will be modified by transformAggregateCall */
                aggref->args = fargs;
+               aggref->aggorder = agg_order;
+               /* aggdistinct will be set by transformAggregateCall */
                aggref->aggstar = agg_star;
-               aggref->aggdistinct = agg_distinct;
+               /* agglevelsup will be set by transformAggregateCall */
                aggref->location = location;
 
                /*
                                         parser_errposition(pstate, location)));
 
                /* parse_agg.c does additional aggregate-specific processing */
-               transformAggregateCall(pstate, aggref);
+               transformAggregateCall(pstate, aggref, agg_distinct);
 
                retval = (Node *) aggref;
        }
                                                        NameListToString(funcname)),
                                         parser_errposition(pstate, location)));
 
+               /*
+                * ordered aggs not allowed in windows yet
+                */
+               if (agg_order != NIL)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("aggregate ORDER BY is not implemented for window functions"),
+                                        parser_errposition(pstate, location)));
+
                if (retset)
                        ereport(ERROR,
                                        (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
 
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *     $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.31 2009/12/07 05:22:22 tgl Exp $
+ *     $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.32 2009/12/15 17:57:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
                funccallnode = makeNode(FuncCall);
                funccallnode->funcname = SystemFuncName("nextval");
                funccallnode->args = list_make1(castnode);
+               funccallnode->agg_order = NIL;
                funccallnode->agg_star = false;
                funccallnode->agg_distinct = false;
                funccallnode->func_variadic = false;
 
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.316 2009/12/07 05:22:22 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.317 2009/12/15 17:57:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 {
        StringInfo      buf = context->buf;
        Oid                     argtypes[FUNC_MAX_ARGS];
+       List       *arglist;
        int                     nargs;
        ListCell   *l;
 
-       if (list_length(aggref->args) > FUNC_MAX_ARGS)
-               ereport(ERROR,
-                               (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
-                                errmsg("too many arguments")));
+       /* Extract the regular arguments, ignoring resjunk stuff for the moment */
+       arglist = NIL;
        nargs = 0;
        foreach(l, aggref->args)
        {
-               Node   *arg = (Node *) lfirst(l);
+               TargetEntry *tle = (TargetEntry *) lfirst(l);
+               Node   *arg = (Node *) tle->expr;
 
                Assert(!IsA(arg, NamedArgExpr));
+               if (tle->resjunk)
+                       continue;
+               if (nargs >= FUNC_MAX_ARGS)                             /* paranoia */
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+                                        errmsg("too many arguments")));
                argtypes[nargs] = exprType(arg);
+               arglist = lappend(arglist, arg);
                nargs++;
        }
 
        appendStringInfo(buf, "%s(%s",
                                         generate_function_name(aggref->aggfnoid, nargs,
                                                                                        NIL, argtypes, NULL),
-                                        aggref->aggdistinct ? "DISTINCT " : "");
+                                        (aggref->aggdistinct != NIL) ? "DISTINCT " : "");
        /* aggstar can be set only in zero-argument aggregates */
        if (aggref->aggstar)
                appendStringInfoChar(buf, '*');
        else
-               get_rule_expr((Node *) aggref->args, context, true);
+               get_rule_expr((Node *) arglist, context, true);
+       if (aggref->aggorder != NIL)
+       {
+               appendStringInfoString(buf, " ORDER BY ");
+               get_rule_orderby(aggref->aggorder, aggref->args, false, context);
+       }
        appendStringInfoChar(buf, ')');
 }
 
 
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.557 2009/12/11 03:34:56 itagaki Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.558 2009/12/15 17:57:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     200912111
+#define CATALOG_VERSION_NO     200912151
 
 #endif
 
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.418 2009/12/11 03:34:56 itagaki Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.419 2009/12/15 17:57:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 /*
  * FuncCall - a function or aggregate invocation
  *
+ * agg_order (if not NIL) indicates we saw 'foo(... ORDER BY ...)'.
  * agg_star indicates we saw a 'foo(*)' construct, while agg_distinct
- * indicates we saw 'foo(DISTINCT ...)'.  In either case, the construct
- * *must* be an aggregate call.  Otherwise, it might be either an
+ * indicates we saw 'foo(DISTINCT ...)'.  In any of these cases, the
+ * construct *must* be an aggregate call.  Otherwise, it might be either an
  * aggregate or some other kind of function.  However, if OVER is present
  * it had better be an aggregate or window function.
  */
        NodeTag         type;
        List       *funcname;           /* qualified name of function */
        List       *args;                       /* the arguments (list of exprs) */
+       List       *agg_order;      /* ORDER BY (list of SortBy) */
        bool            agg_star;               /* argument was really '*' */
        bool            agg_distinct;   /* arguments were labeled DISTINCT */
        bool            func_variadic;  /* last argument was labeled VARIADIC */
 
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.151 2009/10/08 02:39:24 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.152 2009/12/15 17:57:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 /*
  * Aggref
+ *
+ * The aggregate's args list is a targetlist, ie, a list of TargetEntry nodes
+ * (before Postgres 8.5 it was just bare expressions).  The non-resjunk TLEs
+ * represent the aggregate's regular arguments (if any) and resjunk TLEs can
+ * be added at the end to represent ORDER BY expressions that are not also
+ * arguments.  As in a top-level Query, the TLEs can be marked with
+ * ressortgroupref indexes to let them be referenced by SortGroupClause
+ * entries in the aggorder and/or aggdistinct lists.  This represents ORDER BY
+ * and DISTINCT operations to be applied to the aggregate input rows before
+ * they are passed to the transition function.  The grammar only allows a
+ * simple "DISTINCT" specifier for the arguments, but we use the full
+ * query-level representation to allow more code sharing.
  */
 typedef struct Aggref
 {
        Expr            xpr;
        Oid                     aggfnoid;               /* pg_proc Oid of the aggregate */
        Oid                     aggtype;                /* type Oid of result of the aggregate */
-       List       *args;                       /* arguments to the aggregate */
-       Index           agglevelsup;    /* > 0 if agg belongs to outer query */
+       List       *args;                       /* arguments and sort expressions */
+       List       *aggorder;           /* ORDER BY (list of SortGroupClause) */
+       List       *aggdistinct;        /* DISTINCT (list of SortGroupClause) */
        bool            aggstar;                /* TRUE if argument list was really '*' */
-       bool            aggdistinct;    /* TRUE if it's agg(DISTINCT ...) */
+       Index           agglevelsup;    /* > 0 if agg belongs to outer query */
        int                     location;               /* token location, or -1 if unknown */
 } Aggref;
 
 
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/optimizer/clauses.h,v 1.98 2009/06/11 14:49:11 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/clauses.h,v 1.99 2009/12/15 17:57:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 typedef struct
 {
        int                     numAggs;                /* total number of aggregate calls */
-       int                     numDistinctAggs;        /* number that use DISTINCT */
+       int                     numOrderedAggs;         /* number that use DISTINCT or ORDER BY */
        Size            transitionSpace;        /* for pass-by-ref transition data */
 } AggClauseCounts;
 
 
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/parser/parse_agg.h,v 1.39 2009/06/11 14:49:11 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/parser/parse_agg.h,v 1.40 2009/12/15 17:57:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #include "parser/parse_node.h"
 
-extern void transformAggregateCall(ParseState *pstate, Aggref *agg);
+extern void transformAggregateCall(ParseState *pstate, Aggref *agg,
+                                                                  bool agg_distinct);
 extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
                                                WindowDef *windef);
 
 
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/parser/parse_clause.h,v 1.56 2009/08/27 20:08:02 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/parser/parse_clause.h,v 1.57 2009/12/15 17:57:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
                                         const char *constructName);
 extern List *transformGroupClause(ParseState *pstate, List *grouplist,
                                         List **targetlist, List *sortClause,
-                                        bool isWindowFunc);
+                                        bool useSQL99);
 extern List *transformSortClause(ParseState *pstate, List *orderlist,
-                                       List **targetlist, bool resolveUnknown, bool isWindowFunc);
+                                       List **targetlist, bool resolveUnknown, bool useSQL99);
 
 extern List *transformWindowDefinitions(ParseState *pstate,
                                                   List *windowdefs,
                                                   List **targetlist);
 
 extern List *transformDistinctClause(ParseState *pstate,
-                                               List **targetlist, List *sortClause);
+                                               List **targetlist, List *sortClause, bool is_agg);
 extern List *transformDistinctOnClause(ParseState *pstate, List *distinctlist,
                                                  List **targetlist, List *sortClause);
 
 
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/parser/parse_func.h,v 1.66 2009/10/08 02:39:25 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/parser/parse_func.h,v 1.67 2009/12/15 17:57:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 extern Node *ParseFuncOrColumn(ParseState *pstate,
                                  List *funcname, List *fargs,
-                                 bool agg_star, bool agg_distinct, bool func_variadic,
+                                 List *agg_order, bool agg_star, bool agg_distinct,
+                                 bool func_variadic,
                                  WindowDef *over, bool is_column, int location);
 
 extern FuncDetailCode func_get_detail(List *funcname,
 
  9999 | 1
 (3 rows)
 
+--
+-- Test combinations of DISTINCT and/or ORDER BY
+--
+select array_agg(a order by b)
+  from (values (1,4),(2,3),(3,1),(4,2)) v(a,b);
+ array_agg 
+-----------
+ {3,4,2,1}
+(1 row)
+
+select array_agg(a order by a)
+  from (values (1,4),(2,3),(3,1),(4,2)) v(a,b);
+ array_agg 
+-----------
+ {1,2,3,4}
+(1 row)
+
+select array_agg(a order by a desc)
+  from (values (1,4),(2,3),(3,1),(4,2)) v(a,b);
+ array_agg 
+-----------
+ {4,3,2,1}
+(1 row)
+
+select array_agg(b order by a desc)
+  from (values (1,4),(2,3),(3,1),(4,2)) v(a,b);
+ array_agg 
+-----------
+ {2,1,3,4}
+(1 row)
+
+select array_agg(distinct a)
+  from (values (1),(2),(1),(3),(null),(2)) v(a);
+  array_agg   
+--------------
+ {1,2,3,NULL}
+(1 row)
+
+select array_agg(distinct a order by a)
+  from (values (1),(2),(1),(3),(null),(2)) v(a);
+  array_agg   
+--------------
+ {1,2,3,NULL}
+(1 row)
+
+select array_agg(distinct a order by a desc)
+  from (values (1),(2),(1),(3),(null),(2)) v(a);
+  array_agg   
+--------------
+ {NULL,3,2,1}
+(1 row)
+
+select array_agg(distinct a order by a desc nulls last)
+  from (values (1),(2),(1),(3),(null),(2)) v(a);
+  array_agg   
+--------------
+ {3,2,1,NULL}
+(1 row)
+
+-- multi-arg aggs, strict/nonstrict, distinct/order by
+select aggfstr(a,b,c)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c);
+                aggfstr                
+---------------------------------------
+ {"(1,3,foo)","(2,2,bar)","(3,1,baz)"}
+(1 row)
+
+select aggfns(a,b,c)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c);
+                    aggfns                     
+-----------------------------------------------
+ {"(1,3,foo)","(0,,)","(2,2,bar)","(3,1,baz)"}
+(1 row)
+
+select aggfstr(distinct a,b,c)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+       generate_series(1,3) i;
+                aggfstr                
+---------------------------------------
+ {"(1,3,foo)","(2,2,bar)","(3,1,baz)"}
+(1 row)
+
+select aggfns(distinct a,b,c)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+       generate_series(1,3) i;
+                    aggfns                     
+-----------------------------------------------
+ {"(0,,)","(1,3,foo)","(2,2,bar)","(3,1,baz)"}
+(1 row)
+
+select aggfstr(distinct a,b,c order by b)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+       generate_series(1,3) i;
+                aggfstr                
+---------------------------------------
+ {"(3,1,baz)","(2,2,bar)","(1,3,foo)"}
+(1 row)
+
+select aggfns(distinct a,b,c order by b)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+       generate_series(1,3) i;
+                    aggfns                     
+-----------------------------------------------
+ {"(3,1,baz)","(2,2,bar)","(1,3,foo)","(0,,)"}
+(1 row)
+
+-- test specific code paths
+select aggfns(distinct a,a,c order by c using ~<~,a)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+       generate_series(1,2) i;
+                     aggfns                     
+------------------------------------------------
+ {"(2,2,bar)","(3,3,baz)","(1,1,foo)","(0,0,)"}
+(1 row)
+
+select aggfns(distinct a,a,c order by c using ~<~)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+       generate_series(1,2) i;
+                     aggfns                     
+------------------------------------------------
+ {"(2,2,bar)","(3,3,baz)","(1,1,foo)","(0,0,)"}
+(1 row)
+
+select aggfns(distinct a,a,c order by a)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+       generate_series(1,2) i;
+                     aggfns                     
+------------------------------------------------
+ {"(0,0,)","(1,1,foo)","(2,2,bar)","(3,3,baz)"}
+(1 row)
+
+select aggfns(distinct a,b,c order by a,c using ~<~,b)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+       generate_series(1,2) i;
+                    aggfns                     
+-----------------------------------------------
+ {"(0,,)","(1,3,foo)","(2,2,bar)","(3,1,baz)"}
+(1 row)
+
+-- check node I/O via view creation and usage, also deparsing logic
+create view agg_view1 as
+  select aggfns(a,b,c)
+    from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c);
+select * from agg_view1;
+                    aggfns                     
+-----------------------------------------------
+ {"(1,3,foo)","(0,,)","(2,2,bar)","(3,1,baz)"}
+(1 row)
+
+select pg_get_viewdef('agg_view1'::regclass);
+                                                                     pg_get_viewdef                                                                     
+--------------------------------------------------------------------------------------------------------------------------------------------------------
+ SELECT aggfns(v.a, v.b, v.c) AS aggfns FROM (VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
+(1 row)
+
+create or replace view agg_view1 as
+  select aggfns(distinct a,b,c)
+    from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+         generate_series(1,3) i;
+select * from agg_view1;
+                    aggfns                     
+-----------------------------------------------
+ {"(0,,)","(1,3,foo)","(2,2,bar)","(3,1,baz)"}
+(1 row)
+
+select pg_get_viewdef('agg_view1'::regclass);
+                                                                                       pg_get_viewdef                                                                                        
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ SELECT aggfns(DISTINCT v.a, v.b, v.c) AS aggfns FROM (VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c), generate_series(1, 3) i(i);
+(1 row)
+
+create or replace view agg_view1 as
+  select aggfns(distinct a,b,c order by b)
+    from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+         generate_series(1,3) i;
+select * from agg_view1;
+                    aggfns                     
+-----------------------------------------------
+ {"(3,1,baz)","(2,2,bar)","(1,3,foo)","(0,,)"}
+(1 row)
+
+select pg_get_viewdef('agg_view1'::regclass);
+                                                                                              pg_get_viewdef                                                                                              
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ SELECT aggfns(DISTINCT v.a, v.b, v.c ORDER BY v.b) AS aggfns FROM (VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c), generate_series(1, 3) i(i);
+(1 row)
+
+create or replace view agg_view1 as
+  select aggfns(a,b,c order by b+1)
+    from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c);
+select * from agg_view1;
+                    aggfns                     
+-----------------------------------------------
+ {"(3,1,baz)","(2,2,bar)","(1,3,foo)","(0,,)"}
+(1 row)
+
+select pg_get_viewdef('agg_view1'::regclass);
+                                                                              pg_get_viewdef                                                                               
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns FROM (VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
+(1 row)
+
+create or replace view agg_view1 as
+  select aggfns(a,a,c order by b)
+    from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c);
+select * from agg_view1;
+                     aggfns                     
+------------------------------------------------
+ {"(3,3,baz)","(2,2,bar)","(1,1,foo)","(0,0,)"}
+(1 row)
+
+select pg_get_viewdef('agg_view1'::regclass);
+                                                                           pg_get_viewdef                                                                            
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns FROM (VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
+(1 row)
+
+create or replace view agg_view1 as
+  select aggfns(a,b,c order by c using ~<~)
+    from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c);
+select * from agg_view1;
+                    aggfns                     
+-----------------------------------------------
+ {"(2,2,bar)","(3,1,baz)","(1,3,foo)","(0,,)"}
+(1 row)
+
+select pg_get_viewdef('agg_view1'::regclass);
+                                                                                      pg_get_viewdef                                                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns FROM (VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
+(1 row)
+
+create or replace view agg_view1 as
+  select aggfns(distinct a,b,c order by a,c using ~<~,b)
+    from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+         generate_series(1,2) i;
+select * from agg_view1;
+                    aggfns                     
+-----------------------------------------------
+ {"(0,,)","(1,3,foo)","(2,2,bar)","(3,1,baz)"}
+(1 row)
+
+select pg_get_viewdef('agg_view1'::regclass);
+                                                                                                             pg_get_viewdef                                                                                                              
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ SELECT aggfns(DISTINCT v.a, v.b, v.c ORDER BY v.a, v.c USING ~<~ NULLS LAST, v.b) AS aggfns FROM (VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c), generate_series(1, 2) i(i);
+(1 row)
+
+drop view agg_view1;
+-- incorrect DISTINCT usage errors
+select aggfns(distinct a,b,c order by i)
+  from (values (1,1,'foo')) v(a,b,c), generate_series(1,2) i;
+ERROR:  in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list
+LINE 1: select aggfns(distinct a,b,c order by i)
+                                              ^
+select aggfns(distinct a,b,c order by a,b+1)
+  from (values (1,1,'foo')) v(a,b,c), generate_series(1,2) i;
+ERROR:  in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list
+LINE 1: select aggfns(distinct a,b,c order by a,b+1)
+                                                ^
+select aggfns(distinct a,b,c order by a,b,i,c)
+  from (values (1,1,'foo')) v(a,b,c), generate_series(1,2) i;
+ERROR:  in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list
+LINE 1: select aggfns(distinct a,b,c order by a,b,i,c)
+                                                  ^
+select aggfns(distinct a,a,c order by a,b)
+  from (values (1,1,'foo')) v(a,b,c), generate_series(1,2) i;
+ERROR:  in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list
+LINE 1: select aggfns(distinct a,a,c order by a,b)
+                                                ^
 
    sfunc = int8inc_any, stype = int8,
    initcond = '0'
 );
+COMMENT ON AGGREGATE nosuchagg (*) IS 'should fail';
+ERROR:  aggregate nosuchagg(*) does not exist
+COMMENT ON AGGREGATE newcnt (*) IS 'an agg(*) comment';
+COMMENT ON AGGREGATE newcnt ("any") IS 'an agg(any) comment';
 -- multi-argument aggregate
 create function sum3(int8,int8,int8) returns int8 as
 'select $1 + $2 + $3' language sql strict immutable;
    sfunc = sum3, stype = int8,
    initcond = '0'
 );
-COMMENT ON AGGREGATE nosuchagg (*) IS 'should fail';
-ERROR:  aggregate nosuchagg(*) does not exist
-COMMENT ON AGGREGATE newcnt (*) IS 'an agg(*) comment';
-COMMENT ON AGGREGATE newcnt ("any") IS 'an agg(any) comment';
+-- multi-argument aggregates sensitive to distinct/order, strict/nonstrict
+create type aggtype as (a integer, b integer, c text);
+create function aggf_trans(aggtype[],integer,integer,text) returns aggtype[]
+as 'select array_append($1,ROW($2,$3,$4)::aggtype)'
+language sql strict immutable;
+create function aggfns_trans(aggtype[],integer,integer,text) returns aggtype[]
+as 'select array_append($1,ROW($2,$3,$4)::aggtype)'
+language sql immutable;
+create aggregate aggfstr(integer,integer,text) (
+   sfunc = aggf_trans, stype = aggtype[],
+   initcond = '{}'
+);
+create aggregate aggfns(integer,integer,text) (
+   sfunc = aggfns_trans, stype = aggtype[],
+   initcond = '{}'
+);
 
  a_star
  abstime_tbl
  aggtest
+ aggtype
  array_index_op_test
  array_op_test
  arrtest
  toyemp
  varchar_tbl
  xacttest
-(101 rows)
+(102 rows)
 
 SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer')));
  name 
 
 select max(unique2) from tenk1 order by max(unique2);
 select max(unique2) from tenk1 order by max(unique2)+1;
 select max(unique2), generate_series(1,3) as g from tenk1 order by g desc;
+
+--
+-- Test combinations of DISTINCT and/or ORDER BY
+--
+
+select array_agg(a order by b)
+  from (values (1,4),(2,3),(3,1),(4,2)) v(a,b);
+select array_agg(a order by a)
+  from (values (1,4),(2,3),(3,1),(4,2)) v(a,b);
+select array_agg(a order by a desc)
+  from (values (1,4),(2,3),(3,1),(4,2)) v(a,b);
+select array_agg(b order by a desc)
+  from (values (1,4),(2,3),(3,1),(4,2)) v(a,b);
+
+select array_agg(distinct a)
+  from (values (1),(2),(1),(3),(null),(2)) v(a);
+select array_agg(distinct a order by a)
+  from (values (1),(2),(1),(3),(null),(2)) v(a);
+select array_agg(distinct a order by a desc)
+  from (values (1),(2),(1),(3),(null),(2)) v(a);
+select array_agg(distinct a order by a desc nulls last)
+  from (values (1),(2),(1),(3),(null),(2)) v(a);
+
+-- multi-arg aggs, strict/nonstrict, distinct/order by
+
+select aggfstr(a,b,c)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c);
+select aggfns(a,b,c)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c);
+
+select aggfstr(distinct a,b,c)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+       generate_series(1,3) i;
+select aggfns(distinct a,b,c)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+       generate_series(1,3) i;
+
+select aggfstr(distinct a,b,c order by b)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+       generate_series(1,3) i;
+select aggfns(distinct a,b,c order by b)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+       generate_series(1,3) i;
+
+-- test specific code paths
+
+select aggfns(distinct a,a,c order by c using ~<~,a)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+       generate_series(1,2) i;
+select aggfns(distinct a,a,c order by c using ~<~)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+       generate_series(1,2) i;
+select aggfns(distinct a,a,c order by a)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+       generate_series(1,2) i;
+select aggfns(distinct a,b,c order by a,c using ~<~,b)
+  from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+       generate_series(1,2) i;
+
+-- check node I/O via view creation and usage, also deparsing logic
+
+create view agg_view1 as
+  select aggfns(a,b,c)
+    from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c);
+
+select * from agg_view1;
+select pg_get_viewdef('agg_view1'::regclass);
+
+create or replace view agg_view1 as
+  select aggfns(distinct a,b,c)
+    from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+         generate_series(1,3) i;
+
+select * from agg_view1;
+select pg_get_viewdef('agg_view1'::regclass);
+
+create or replace view agg_view1 as
+  select aggfns(distinct a,b,c order by b)
+    from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+         generate_series(1,3) i;
+
+select * from agg_view1;
+select pg_get_viewdef('agg_view1'::regclass);
+
+create or replace view agg_view1 as
+  select aggfns(a,b,c order by b+1)
+    from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c);
+
+select * from agg_view1;
+select pg_get_viewdef('agg_view1'::regclass);
+
+create or replace view agg_view1 as
+  select aggfns(a,a,c order by b)
+    from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c);
+
+select * from agg_view1;
+select pg_get_viewdef('agg_view1'::regclass);
+
+create or replace view agg_view1 as
+  select aggfns(a,b,c order by c using ~<~)
+    from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c);
+
+select * from agg_view1;
+select pg_get_viewdef('agg_view1'::regclass);
+
+create or replace view agg_view1 as
+  select aggfns(distinct a,b,c order by a,c using ~<~,b)
+    from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+         generate_series(1,2) i;
+
+select * from agg_view1;
+select pg_get_viewdef('agg_view1'::regclass);
+
+drop view agg_view1;
+
+-- incorrect DISTINCT usage errors
+
+select aggfns(distinct a,b,c order by i)
+  from (values (1,1,'foo')) v(a,b,c), generate_series(1,2) i;
+select aggfns(distinct a,b,c order by a,b+1)
+  from (values (1,1,'foo')) v(a,b,c), generate_series(1,2) i;
+select aggfns(distinct a,b,c order by a,b,i,c)
+  from (values (1,1,'foo')) v(a,b,c), generate_series(1,2) i;
+select aggfns(distinct a,a,c order by a,b)
+  from (values (1,1,'foo')) v(a,b,c), generate_series(1,2) i;
 
    initcond = '0'
 );
 
+COMMENT ON AGGREGATE nosuchagg (*) IS 'should fail';
+COMMENT ON AGGREGATE newcnt (*) IS 'an agg(*) comment';
+COMMENT ON AGGREGATE newcnt ("any") IS 'an agg(any) comment';
+
 -- multi-argument aggregate
 create function sum3(int8,int8,int8) returns int8 as
 'select $1 + $2 + $3' language sql strict immutable;
    initcond = '0'
 );
 
-COMMENT ON AGGREGATE nosuchagg (*) IS 'should fail';
-COMMENT ON AGGREGATE newcnt (*) IS 'an agg(*) comment';
-COMMENT ON AGGREGATE newcnt ("any") IS 'an agg(any) comment';
+-- multi-argument aggregates sensitive to distinct/order, strict/nonstrict
+create type aggtype as (a integer, b integer, c text);
+
+create function aggf_trans(aggtype[],integer,integer,text) returns aggtype[]
+as 'select array_append($1,ROW($2,$3,$4)::aggtype)'
+language sql strict immutable;
+
+create function aggfns_trans(aggtype[],integer,integer,text) returns aggtype[]
+as 'select array_append($1,ROW($2,$3,$4)::aggtype)'
+language sql immutable;
+
+create aggregate aggfstr(integer,integer,text) (
+   sfunc = aggf_trans, stype = aggtype[],
+   initcond = '{}'
+);
+
+create aggregate aggfns(integer,integer,text) (
+   sfunc = aggfns_trans, stype = aggtype[],
+   initcond = '{}'
+);