*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 352,358 ****
        <entry><structfield>aggtransfn</structfield></entry>
        <entry><type>regproc</type></entry>
        <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
!       <entry>Transition function</entry>
       </row>
       <row>
        <entry><structfield>aggfinalfn</structfield></entry>
--- 352,358 ----
        <entry><structfield>aggtransfn</structfield></entry>
        <entry><type>regproc</type></entry>
        <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
!       <entry>Transition function (zero if none)</entry>
       </row>
       <row>
        <entry><structfield>aggfinalfn</structfield></entry>
***************
*** 370,376 ****
        <entry><structfield>aggtranstype</structfield></entry>
        <entry><type>oid</type></entry>
        <entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
!       <entry>Data type of the aggregate function's internal transition (state) data</entry>
       </row>
       <row>
        <entry><structfield>aggtransspace</structfield></entry>
--- 370,394 ----
        <entry><structfield>aggtranstype</structfield></entry>
        <entry><type>oid</type></entry>
        <entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
!       <entry>Data type of the aggregate function's internal transition (state) data (zero if none)</entry>
!      </row>
!      <row>
!       <entry><structfield>aggtranssortop</structfield></entry>
!       <entry><type>oid</type></entry>
!       <entry><literal><link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.oid</literal></entry>
!       <entry>An optional sort operator for the type "aggtranstype", used for some kinds of ordered set functions</entry>
!      </row>
!      <row>
!       <entry><structfield>aggordnargs</structfield></entry>
!       <entry><type>int4</type></entry>
!       <entry></entry>
!       <entry>Number of direct arguments to ordered set function; -2 for hypothetical set functions; -1 for ordinary aggregates.</entry>
!      </row>
!      <row>
!       <entry><structfield>aggisordsetfunc</structfield></entry>
!       <entry><type>bool</type></entry>
!       <entry></entry>
!       <entry>A flag to represent whether a function is ordered set or not</entry>
       </row>
       <row>
        <entry><structfield>aggtransspace</structfield></entry>
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 12294,12299 **** SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab;
--- 12294,12542 ----
  
   </sect1>
  
+  <sect1 id="functions-ordered">
+   <title>Ordered Set Functions</title>
+ 
+   <indexterm zone="functions-ordered">
+    <primary>ordered set function</primary>
+    <secondary>built-in</secondary>
+   </indexterm>
+ 
+   <para>
+    <firstterm>Ordered set functions</firstterm> compute a single result
+    from an ordered set of input values.  The built-in ordered set functions
+    are listed in
+    <xref linkend="functions-inversedist-table"> and
+    <xref linkend="functions-hypothetical-table">.
+    The special syntax considerations for ordered set functions
+    are explained in <xref linkend="syntax-orderedset">.
+   </para>
+ 
+   <table id="functions-inversedist-table">
+    <title>Inverse Distribution Functions</title>
+ 
+    <tgroup cols="5">
+     <thead>
+      <row>
+       <entry>Function</entry>
+       <entry>Direct Argument Type(s)</entry>
+       <entry>Ordered Argument Type(s)</entry>
+       <entry>Return Type</entry>
+       <entry>Description</entry>
+      </row>
+     </thead>
+ 
+     <tbody>
+ 
+      <row>
+       <entry>
+        <indexterm>
+         <primary>percentile</primary>
+         <secondary>discrete</secondary>
+        </indexterm>
+        <function>percentile_disc(<replaceable class="parameter">fraction</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+       </entry>
+       <entry>
+        <type>double precision</type> (must be [0..1])
+       </entry>
+       <entry>
+        any sortable type
+       </entry>
+       <entry>
+        same as sort expression
+       </entry>
+       <entry>
+         discrete percentile; returns the first result whose position in
+         the ordering equals or exceeds the specified fraction
+       </entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>percentile_disc(<replaceable class="parameter">fractions</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+       </entry>
+       <entry>
+        <type>double precision[]</type> (all must be [0..1] or null)
+       </entry>
+       <entry>
+        any sortable type
+       </entry>
+       <entry>
+        array of input type
+       </entry>
+       <entry>
+         multiple discrete percentile; returns an array of results matching the
+         shape of the <literal>fractions</literal> parameter, with each
+         non-null element replaced by the input value at that percentile
+       </entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <indexterm>
+         <primary>percentile</primary>
+         <secondary>continuous</secondary>
+        </indexterm>
+        <indexterm>
+         <primary>median</primary>
+        </indexterm>
+        <function>percentile_cont(<replaceable class="parameter">fraction</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+       </entry>
+       <entry>
+        <type>double precision</type> (must be [0..1])
+       </entry>
+       <entry>
+        <type>double precision</type> or <type>interval</type>
+       </entry>
+       <entry>
+        same as sort expression
+       </entry>
+       <entry>
+         continuous percentile; interpolates between adjacent items.
+       </entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>percentile_cont(<replaceable class="parameter">fractions</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+       </entry>
+       <entry>
+        <type>double precision[]</type> (all must be [0..1] or null)
+       </entry>
+       <entry>
+        <type>double precision</type> or <type>interval</type>
+       </entry>
+       <entry>
+        array of input type
+       </entry>
+       <entry>
+         multiple continuous percentile; returns an array of results matching
+         the shape of the <literal>fractions</literal> parameter, with each
+         non-null element replaced by the value corresponding to that percentile
+       </entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <indexterm>
+         <primary>mode</primary>
+         <secondary>statistical</secondary>
+        </indexterm>
+        <function>mode() WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+       </entry>
+       <entry>
+       </entry>
+       <entry>
+        any sortable type
+       </entry>
+       <entry>
+        same as sort expression
+       </entry>
+       <entry>
+        returns the most frequent input value (choosing one arbitrarily if
+        there are multiple equally good results)
+       </entry>
+      </row>
+ 
+     </tbody>
+    </tgroup>
+   </table>
+ 
+   <para>
+    All the inverse distribution functions ignore null values in their sorted
+    input.  The <replaceable>fraction</replaceable> parameter must be between 0
+    and 1; an error is thrown if not.  However, a null fraction simply produces
+    a null result.
+   </para>
+ 
+   <table id="functions-hypothetical-table">
+    <title>Hypothetical Set Functions</title>
+ 
+    <tgroup cols="2">
+     <thead>
+      <row>
+       <entry>Function</entry>
+       <entry>Return Type</entry>
+      </row>
+     </thead>
+ 
+     <tbody>
+ 
+      <row>
+       <entry>
+        <indexterm>
+         <primary>rank</primary>
+         <secondary>hypothetical</secondary>
+        </indexterm>
+        <function>rank(<replaceable class="parameter">args</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sorted_args</replaceable>)</function>
+       </entry>
+       <entry>
+        <type>bigint</type>
+       </entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <indexterm>
+         <primary>dense_rank</primary>
+         <secondary>hypothetical</secondary>
+        </indexterm>
+        <function>dense_rank(<replaceable class="parameter">args</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sorted_args</replaceable>)</function>
+       </entry>
+       <entry>
+        <type>bigint</type>
+       </entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <indexterm>
+         <primary>percent_rank</primary>
+         <secondary>hypothetical</secondary>
+        </indexterm>
+        <function>percent_rank(<replaceable class="parameter">args</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sorted_args</replaceable>)</function>
+       </entry>
+       <entry>
+        <type>double precision</type>
+       </entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <indexterm>
+         <primary>cume_dist</primary>
+         <secondary>hypothetical</secondary>
+        </indexterm>
+        <function>cume_dist(<replaceable class="parameter">args</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sorted_args</replaceable>)</function>
+       </entry>
+       <entry>
+        <type>double precision</type>
+       </entry>
+      </row>
+ 
+     </tbody>
+    </tgroup>
+   </table>
+ 
+   <para>
+     For all hypothetical set functions, the list of arguments given
+     by <replaceable>args</replaceable> should match the number and types of
+     arguments given as <replaceable>sorted_args</replaceable>.
+   </para>
+ 
+   <para>
+     All of the functions listed in
+     <xref linkend="functions-hypothetical-table"> are associated with a
+     window function defined in
+     <xref linkend="functions-window">.  In each case, the function result
+     represents the value that the associated window function would have
+     returned, for the hypothetical row constructed from
+     <replaceable>args</replaceable> and included in the sorted group of
+     rows.
+    </para>
+ 
+  </sect1>
+ 
   <sect1 id="functions-window">
    <title>Window Functions</title>
  
*** a/doc/src/sgml/ref/alter_aggregate.sgml
--- b/doc/src/sgml/ref/alter_aggregate.sgml
***************
*** 21,32 **** PostgreSQL documentation
  
   <refsynopsisdiv>
  <synopsis>
! ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
    RENAME TO <replaceable>new_name</replaceable>
  ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
    OWNER TO <replaceable>new_owner</replaceable>
  ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
    SET SCHEMA <replaceable>new_schema</replaceable>
  </synopsis>
   </refsynopsisdiv>
  
--- 21,37 ----
  
   <refsynopsisdiv>
  <synopsis>
! ALTER AGGREGATE
    RENAME TO <replaceable>new_name</replaceable>
  ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
    OWNER TO <replaceable>new_owner</replaceable>
  ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
    SET SCHEMA <replaceable>new_schema</replaceable>
+ 
+ <phrase>where <replaceable>aggregate_signature</replaceable> is one of:</phrase>
+ 
+ <replaceable>name</replaceable> ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
+ <replaceable>name</replaceable> ( [ [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] ] ) WITHIN GROUP ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
  </synopsis>
   </refsynopsisdiv>
  
***************
*** 148,157 **** ALTER AGGREGATE myavg(integer) OWNER TO joe;
    </para>
  
    <para>
!    To move the aggregate function <literal>myavg</literal> for type
!    <type>integer</type> into schema <literal>myschema</literal>:
  <programlisting>
! ALTER AGGREGATE myavg(integer) SET SCHEMA myschema;
  </programlisting></para>
   </refsect1>
  
--- 153,163 ----
    </para>
  
    <para>
!    To move the ordered set function <literal>mypercentile</literal> with
!    direct argument of type <type>float8</type> taking groups
!    of <type>integer</type> type into schema <literal>myschema</literal>:
  <programlisting>
! ALTER AGGREGATE mypercentile(float8) WITHIN GROUP (integer) SET SCHEMA myschema;
  </programlisting></para>
   </refsect1>
  
*** a/doc/src/sgml/ref/alter_extension.sgml
--- b/doc/src/sgml/ref/alter_extension.sgml
***************
*** 30,36 **** ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> DROP <replacea
  
  <phrase>where <replaceable class="PARAMETER">member_object</replaceable> is:</phrase>
  
!   AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) |
    CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
    COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
    CONVERSION <replaceable class="PARAMETER">object_name</replaceable> |
--- 30,36 ----
  
  <phrase>where <replaceable class="PARAMETER">member_object</replaceable> is:</phrase>
  
!   AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) [ WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) ] |
    CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
    COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
    CONVERSION <replaceable class="PARAMETER">object_name</replaceable> |
*** a/doc/src/sgml/ref/comment.sgml
--- b/doc/src/sgml/ref/comment.sgml
***************
*** 23,29 **** PostgreSQL documentation
  <synopsis>
  COMMENT ON
  {
!   AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) |
    CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
    COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
    COLUMN <replaceable class="PARAMETER">relation_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
--- 23,29 ----
  <synopsis>
  COMMENT ON
  {
!   AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) [ WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) ] |
    CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
    COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
    COLUMN <replaceable class="PARAMETER">relation_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
*** a/doc/src/sgml/ref/create_aggregate.sgml
--- b/doc/src/sgml/ref/create_aggregate.sgml
***************
*** 30,35 **** CREATE AGGREGATE <replaceable class="parameter">name</replaceable> ( [ <replacea
--- 30,44 ----
      [ , SORTOP = <replaceable class="PARAMETER">sort_operator</replaceable> ]
  )
  
+ CREATE AGGREGATE <replaceable class="parameter">name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">arg_name</replaceable> ] <replaceable class="parameter">arg_data_type</replaceable> [ , ... ] ) WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">arg_name</replaceable> ] <replaceable class="parameter">arg_data_type</replaceable> [ , ... ] ) (
+     FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable>
+     [ , STRICT ]
+     [ , HYPOTHETICAL ]
+     [ , STYPE = <replaceable class="PARAMETER">state_data_type</replaceable> ]
+     [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
+     [ , TRANSSORTOP = <replaceable class="PARAMETER">state_sort_operator</replaceable> ]
+ )
+ 
  <phrase>or the old syntax</phrase>
  
  CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
***************
*** 72,78 **** CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
    </para>
  
    <para>
!    An aggregate function is made from one or two ordinary
     functions:
     a state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
--- 81,87 ----
    </para>
  
    <para>
!    An ordinary aggregate function is made from one or two ordinary
     functions:
     a state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
***************
*** 167,172 **** SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
--- 176,189 ----
    </para>
  
    <para>
+    The <literal>WITHIN GROUP</literal> syntax denotes a special subset of
+    aggregate functions collectively called <quote>ordered set
+    functions</quote>. These functions operate over groups of sorted values
+    in order-dependent ways. As such, they are constructed differently; there
+    is no state transition function, but the final function is required.
+   </para>
+ 
+   <para>
     To be able to create an aggregate function, you must
     have <literal>USAGE</literal> privilege on the argument types, the state
     type, and the return type, as well as <literal>EXECUTE</literal> privilege
***************
*** 296,301 **** SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
--- 313,323 ----
        aggregate's result, and the return type is <replaceable
        class="PARAMETER">state_data_type</replaceable>.
       </para>
+      <para>
+       For ordered set functions, the function arguments must instead
+       correspond to the input arguments (both direct and grouped) plus
+       the state type if any.
+      </para>
      </listitem>
     </varlistentry>
  
***************
*** 323,328 **** SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
--- 345,383 ----
       </para>
      </listitem>
     </varlistentry>
+ 
+    <varlistentry>
+     <term><replaceable class="PARAMETER">state_sort_operator</replaceable></term>
+     <listitem>
+      <para>
+       For ordered set functions only, this is a sort operator that can be
+       applied to
+       the <replaceable class="PARAMETER">state_data_type</replaceable>.
+       This is just an operator name (possibly schema-qualified).
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>STRICT</literal></term>
+     <listitem>
+      <para>
+       For ordered set functions only, this flag specifies that the function is
+       strict, i.e. that grouped rows containing nulls are skipped.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>HYPOTHETICAL</literal></term>
+     <listitem>
+      <para>
+       For ordered set functions only, this flag specifies that the aggregate
+       parameters are to be processed according to the requirements for
+       hypothetical set functions.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
  
    <para>
*** a/doc/src/sgml/ref/drop_aggregate.sgml
--- b/doc/src/sgml/ref/drop_aggregate.sgml
***************
*** 21,29 **** PostgreSQL documentation
  
   <refsynopsisdiv>
  <synopsis>
! DROP AGGREGATE [ IF EXISTS ]
!   <replaceable class="parameter">name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">arg_name</replaceable> ] <replaceable class="parameter">arg_data_type</replaceable> [ , ... ] )
    [ CASCADE | RESTRICT ]
  </synopsis>
   </refsynopsisdiv>
  
--- 21,33 ----
  
   <refsynopsisdiv>
  <synopsis>
! DROP AGGREGATE [ IF EXISTS ] <replaceable class="parameter">aggregate_signature</replaceable>
    [ CASCADE | RESTRICT ]
+ 
+ <phrase>where <replaceable>aggregate_signature</replaceable> is one of:</phrase>
+ 
+ <replaceable>name</replaceable> ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
+ <replaceable>name</replaceable> ( [ [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] ] ) WITHIN GROUP ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
  </synopsis>
   </refsynopsisdiv>
  
*** a/doc/src/sgml/ref/security_label.sgml
--- b/doc/src/sgml/ref/security_label.sgml
***************
*** 25,31 **** SECURITY LABEL [ FOR <replaceable class="PARAMETER">provider</replaceable> ] ON
  {
    TABLE <replaceable class="PARAMETER">object_name</replaceable> |
    COLUMN <replaceable class="PARAMETER">table_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
!   AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) |
    DATABASE <replaceable class="PARAMETER">object_name</replaceable> |
    DOMAIN <replaceable class="PARAMETER">object_name</replaceable> |
    EVENT TRIGGER <replaceable class="PARAMETER">object_name</replaceable> |
--- 25,31 ----
  {
    TABLE <replaceable class="PARAMETER">object_name</replaceable> |
    COLUMN <replaceable class="PARAMETER">table_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
!   AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) [ WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) ] |
    DATABASE <replaceable class="PARAMETER">object_name</replaceable> |
    DOMAIN <replaceable class="PARAMETER">object_name</replaceable> |
    EVENT TRIGGER <replaceable class="PARAMETER">object_name</replaceable> |
*** a/doc/src/sgml/syntax.sgml
--- b/doc/src/sgml/syntax.sgml
***************
*** 1706,1711 **** SELECT string_agg(a ORDER BY a, ',') FROM table;  -- incorrect
--- 1706,1759 ----
     </para>
    </sect2>
  
+   <sect2 id="syntax-orderedset">
+    <title>Ordered Set Functions</title>
+ 
+    <indexterm zone="syntax-orderedset">
+     <primary>ordered set function</primary>
+    </indexterm>
+ 
+    <indexterm zone="syntax-orderedset">
+     <primary>aggregate function</primary>
+     <secondary>ordered set function</secondary>
+    </indexterm>
+ 
+    <indexterm zone="syntax-orderedset">
+     <primary>WITHIN GROUP</primary>
+    </indexterm>
+ 
+    <para>
+     An <firstterm>ordered set function</firstterm> is a particular kind of
+     aggregate function which is applied to sorted groups of values and returns
+     a single result for each group which may be influenced by the sort
+     order. Like all aggregate functions, it reduces multiple inputs to a
+     single output value; typical ordered set functions return a percentile
+     extracted from the ordered group, or the rank a specified value would have
+     within that group.  The syntax of an ordered set function is:
+ 
+ <synopsis>
+ <replaceable>function_name</replaceable> ( [ <replaceable>expression</replaceable> [ , ... ] ] ) WITHIN GROUP ( <replaceable>order_by_clause</replaceable> ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+ </synopsis>
+ 
+     where <replaceable>function_name</replaceable> is a previously
+     defined ordered set function (possibly qualified with a schema name) and
+     <replaceable>expression</replaceable> is any value expression that does
+     not itself contain an aggregate expression, a window function call, or any
+     reference to ungrouped columns of the source data.  The
+     mandatory <replaceable>order_by_clause</replaceable> 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.  The
+     expressions of the <replaceable>order_by_clause</replaceable> may, and
+     almost invariably do, refer to the ungrouped columns of the input; the
+     clause defines the grouped input to the function. The optional
+     <replaceable>filter_clause</replaceable> is identical to that for
+     aggregate functions (see <xref linkend="syntax-aggregates">, and is applied
+     to input rows prior to the sort operation.
+    </para>
+ 
+   </sect2>
+ 
    <sect2 id="syntax-window-functions">
     <title>Window Function Calls</title>
  
*** a/doc/src/sgml/xaggr.sgml
--- b/doc/src/sgml/xaggr.sgml
***************
*** 9,28 ****
    </indexterm>
  
    <para>
!    Aggregate functions  in <productname>PostgreSQL</productname>
!    are expressed in terms of <firstterm>state values</firstterm>
!    and <firstterm>state transition functions</firstterm>.
!    That is, an aggregate operates using a state value that is updated
!    as each successive input row is processed.
!    To define a new aggregate
!    function, one selects a data type for the state value,
!    an initial value for the state, and a state transition
!    function.  The state transition function is just an
!    ordinary function that could also be used outside the
!    context of the aggregate.  A <firstterm>final function</firstterm>
!    can also be specified, in case the desired result of the aggregate
!    is different from the data that needs to be kept in the running
!    state value.
    </para>
  
    <para>
--- 9,25 ----
    </indexterm>
  
    <para>
!    Aggregate functions (other than ordered set functions)
!    in <productname>PostgreSQL</productname> are expressed in terms
!    of <firstterm>state values</firstterm> and <firstterm>state transition
!    functions</firstterm>.  That is, an aggregate operates using a state value
!    that is updated as each successive input row is processed.  To define a new
!    aggregate function, one selects a data type for the state value, an initial
!    value for the state, and a state transition function.  The state transition
!    function is just an ordinary function that could also be used outside the
!    context of the aggregate.  A <firstterm>final function</firstterm> can also
!    be specified, in case the desired result of the aggregate is different from
!    the data that needs to be kept in the running state value.
    </para>
  
    <para>
*** a/src/backend/catalog/pg_aggregate.c
--- b/src/backend/catalog/pg_aggregate.c
***************
*** 46,51 **** Oid
--- 46,52 ----
  AggregateCreate(const char *aggName,
  				Oid aggNamespace,
  				int numArgs,
+ 				int numDirectArgs,
  				oidvector *parameterTypes,
  				Datum allParameterTypes,
  				Datum parameterModes,
***************
*** 54,78 **** AggregateCreate(const char *aggName,
  				List *aggtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
  				int32 aggTransSpace,
! 				const char *agginitval)
  {
  	Relation	aggdesc;
  	HeapTuple	tup;
  	bool		nulls[Natts_pg_aggregate];
  	Datum		values[Natts_pg_aggregate];
  	Form_pg_proc proc;
! 	Oid			transfn;
! 	Oid			finalfn = InvalidOid;	/* can be omitted */
! 	Oid			sortop = InvalidOid;	/* can be omitted */
  	Oid		   *aggArgTypes = parameterTypes->values;
  	bool		hasPolyArg;
  	bool		hasInternalArg;
  	Oid			rettype;
  	Oid			finaltype;
! 	Oid		   *fnArgs;
! 	int			nargs_transfn;
  	Oid			procOid;
  	TupleDesc	tupDesc;
  	int			i;
--- 55,84 ----
  				List *aggtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
+ 				List *aggtranssortopName,
  				Oid aggTransType,
  				int32 aggTransSpace,
! 				const char *agginitval,
! 				bool isStrict,
! 				bool isOrderedSet,
! 				bool isHypotheticalSet)
  {
  	Relation	aggdesc;
  	HeapTuple	tup;
  	bool		nulls[Natts_pg_aggregate];
  	Datum		values[Natts_pg_aggregate];
  	Form_pg_proc proc;
! 	Oid			transfn = InvalidOid;		/* can be omitted */
! 	Oid			finalfn = InvalidOid;		/* can be omitted */
! 	Oid			sortop = InvalidOid;		/* can be omitted */
! 	Oid			transsortop = InvalidOid;	/* can be omitted */
  	Oid		   *aggArgTypes = parameterTypes->values;
  	bool		hasPolyArg;
  	bool		hasInternalArg;
+ 	Oid         variadic_type = InvalidOid;
  	Oid			rettype;
  	Oid			finaltype;
! 	Oid		   *fnArgs = palloc((numArgs + 1) * sizeof(Oid));
  	Oid			procOid;
  	TupleDesc	tupDesc;
  	int			i;
***************
*** 84,91 **** AggregateCreate(const char *aggName,
  	if (!aggName)
  		elog(ERROR, "no aggregate name supplied");
  
! 	if (!aggtransfnName)
! 		elog(ERROR, "aggregate must have a transition function");
  
  	/* check for polymorphic and INTERNAL arguments */
  	hasPolyArg = false;
--- 90,109 ----
  	if (!aggName)
  		elog(ERROR, "no aggregate name supplied");
  
! 	if (isOrderedSet)
! 	{
! 		if (aggtransfnName)
! 			elog(ERROR, "ordered set functions cannot have transition functions");
! 		if (!aggfinalfnName)
! 			elog(ERROR, "ordered set functions must have final functions");
! 	}
! 	else
! 	{
! 		if (!aggtransfnName)
! 			elog(ERROR, "aggregate must have a transition function");
! 		if (isStrict)
! 			elog(ERROR, "aggregate with transition function must not be explicitly STRICT");
! 	}
  
  	/* check for polymorphic and INTERNAL arguments */
  	hasPolyArg = false;
***************
*** 98,103 **** AggregateCreate(const char *aggName,
--- 116,251 ----
  			hasInternalArg = true;
  	}
  
+ 	/*-
+ 	 * Argument mode checks. If there were no variadics, we should have been
+ 	 * passed a NULL pointer for parameterModes, so we can skip this if so.
+ 	 * Otherwise, the allowed cases are as follows:
+ 	 *
+ 	 * aggfn(..., variadic sometype)   - normal agg with variadic arg last
+ 	 * aggfn(..., variadic "any")      - normal agg with "any" variadic
+ 	 *
+ 	 * ordfn(..., variadic "any") within group (*)
+ 	 *  - ordered set func with "any" variadic in direct args, which requires
+ 	 *    that the ordered args also be variadic any which we represent
+ 	 *    specially; this is the common case for hypothetical set functions.
+ 	 *    Note this is the only case where numDirectArgs == numArgs on input
+ 	 *    (implies finalfn(..., variadic "any"))
+ 	 *
+ 	 * ordfn(...) within group (..., variadic "any")
+ 	 *  - ordered set func with no variadic in direct args, but allowing any
+ 	 *    types of ordered args.
+ 	 *    (implies finalfn(..., ..., variadic "any"))
+ 	 *
+ 	 * We don't allow variadic ordered args other than "any"; we don't allow
+ 	 * anything after variadic "any" except the special-case (*).
+ 	 *
+ 	 * We might like to support this one:
+ 	 *
+ 	 * ordfn(..., variadic sometype) within group (...)
+ 	 *  - ordered set func with variadic direct arg last, followed by ordered
+ 	 *    args, none of which are variadic
+ 	 *    (implies finalfn(..., sometype, ..., [transtype]))
+ 	 *
+ 	 * but currently it seems to be too intrusive to do so; the assumption
+ 	 * that variadic args can only come last is quite widespread.
+ 	 */
+ 
+ 	if (parameterModes != PointerGetDatum(NULL))
+ 	{
+ 		/*
+ 		 * We expect the array to be a 1-D CHAR array; verify that. We don't
+ 		 * need to use deconstruct_array() since the array data is just going
+ 		 * to look like a C array of char values.
+ 		 */
+ 		ArrayType  *modesArray = (ArrayType *) DatumGetPointer(parameterModes);
+ 		char       *paramModes;
+ 		int         modesCount;
+ 		int         i;
+ 
+ 		if (ARR_NDIM(modesArray) != 1 ||
+ 			ARR_HASNULL(modesArray) ||
+ 			ARR_ELEMTYPE(modesArray) != CHAROID)
+ 			elog(ERROR, "parameterModes is not a 1-D char array");
+ 
+ 		paramModes = (char *) ARR_DATA_PTR(modesArray);
+ 		modesCount = ARR_DIMS(modesArray)[0];
+ 
+ 		for (i = 0; i < modesCount; ++i)
+ 		{
+ 			switch (paramModes[i])
+ 			{
+ 				case PROARGMODE_VARIADIC:
+ 					if (OidIsValid(variadic_type))
+ 						ereport(ERROR,
+ 								(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 								 errmsg("VARIADIC must not be specified more than once")));
+ 					variadic_type = aggArgTypes[i];
+ 
+ 					/* enforce restrictions on ordered args */
+ 
+ 					if (numDirectArgs >= 0
+ 						&& i >= numDirectArgs
+ 						&& variadic_type != ANYOID)
+ 						ereport(ERROR,
+ 								(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 								 errmsg("VARIADIC ordered arguments must be of type ANY")));
+ 
+ 					break;
+ 
+ 				case PROARGMODE_IN:
+ 					if (OidIsValid(variadic_type))
+ 						ereport(ERROR,
+ 								(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 								 errmsg("VARIADIC argument must be last")));
+ 					break;
+ 
+ 				default:
+ 					elog(ERROR, "invalid argument mode");
+ 			}
+ 		}
+ 	}
+ 
+ 	switch (variadic_type)
+ 	{
+ 		case InvalidOid:
+ 		case ANYARRAYOID:
+ 		case ANYOID:
+ 			/* okay */
+ 			break;
+ 		default:
+ 			if (!OidIsValid(get_element_type(variadic_type)))
+ 				elog(ERROR, "VARIADIC parameter must be an array");
+ 			break;
+ 	}
+ 
+ 	if (isHypotheticalSet)
+ 	{
+ 		if (numArgs != numDirectArgs
+ 			|| variadic_type != ANYOID)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 					 errmsg("invalid argument types for hypothetical set function"),
+ 					 errhint("Required declaration is (..., VARIADIC \"any\") WITHIN GROUP (*)")));
+ 
+ 		/* flag for special processing for hypothetical sets */
+ 		numDirectArgs = -2;
+ 	}
+ 	else if (numArgs == numDirectArgs)
+ 	{
+ 		if (variadic_type == ANYOID)
+ 		{
+ 			/*
+ 			 * this case allows the number of direct args to be truly variable
+ 			 */
+ 			numDirectArgs = -1;
+ 		}
+ 		else
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 					 errmsg("invalid argument types for ordered set function"),
+ 					 errhint("WITHIN GROUP (*) is not allowed without VARIADIC \"any\"")));
+ 	}
+ 
  	/*
  	 * If transtype is polymorphic, must have polymorphic argument also; else
  	 * we will have no way to deduce the actual transtype.
***************
*** 108,160 **** AggregateCreate(const char *aggName,
  				 errmsg("cannot determine transition data type"),
  				 errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument.")));
  
! 	/* find the transfn */
! 	nargs_transfn = numArgs + 1;
! 	fnArgs = (Oid *) palloc(nargs_transfn * sizeof(Oid));
! 	fnArgs[0] = aggTransType;
! 	memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
! 	transfn = lookup_agg_function(aggtransfnName, nargs_transfn, fnArgs,
! 								  &rettype);
  
! 	/*
! 	 * Return type of transfn (possibly after refinement by
! 	 * enforce_generic_type_consistency, if transtype isn't polymorphic) must
! 	 * exactly match declared transtype.
! 	 *
! 	 * In the non-polymorphic-transtype case, it might be okay to allow a
! 	 * rettype that's binary-coercible to transtype, but I'm not quite
! 	 * convinced that it's either safe or useful.  When transtype is
! 	 * polymorphic we *must* demand exact equality.
! 	 */
! 	if (rettype != aggTransType)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_DATATYPE_MISMATCH),
! 				 errmsg("return type of transition function %s is not %s",
! 						NameListToString(aggtransfnName),
! 						format_type_be(aggTransType))));
  
! 	tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn));
! 	if (!HeapTupleIsValid(tup))
! 		elog(ERROR, "cache lookup failed for function %u", transfn);
! 	proc = (Form_pg_proc) GETSTRUCT(tup);
  
! 	/*
! 	 * If the transfn is strict and the initval is NULL, make sure first input
! 	 * type and transtype are the same (or at least binary-compatible), so
! 	 * that it's OK to use the first input value as the initial transValue.
! 	 */
! 	if (proc->proisstrict && agginitval == NULL)
! 	{
! 		if (numArgs < 1 ||
! 			!IsBinaryCoercible(aggArgTypes[0], aggTransType))
  			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
  	}
- 	ReleaseSysCache(tup);
  
  	/* handle finalfn, if supplied */
! 	if (aggfinalfnName)
  	{
  		fnArgs[0] = aggTransType;
  		finalfn = lookup_agg_function(aggfinalfnName, 1, fnArgs,
--- 256,341 ----
  				 errmsg("cannot determine transition data type"),
  				 errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument.")));
  
! 	if (!isOrderedSet)
! 	{
! 		/* find the transfn */		
  
! 		fnArgs[0] = aggTransType;
! 		memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
  
! 		transfn = lookup_agg_function(aggtransfnName, numArgs + 1, fnArgs,
! 									  &rettype);
  
! 		/*
! 		 * Return type of transfn (possibly after refinement by
! 		 * enforce_generic_type_consistency, if transtype isn't polymorphic)
! 		 * must exactly match declared transtype.
! 		 *
! 		 * In the non-polymorphic-transtype case, it might be okay to allow a
! 		 * rettype that's binary-coercible to transtype, but I'm not quite
! 		 * convinced that it's either safe or useful.  When transtype is
! 		 * polymorphic we *must* demand exact equality.
! 		 */
! 		if (rettype != aggTransType)
  			ereport(ERROR,
! 					(errcode(ERRCODE_DATATYPE_MISMATCH),
! 					 errmsg("return type of transition function %s is not %s",
! 							NameListToString(aggtransfnName),
! 							format_type_be(aggTransType))));
! 
! 		tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn));
! 		if (!HeapTupleIsValid(tup))
! 			elog(ERROR, "cache lookup failed for function %u", transfn);
! 		proc = (Form_pg_proc) GETSTRUCT(tup);
! 
! 		/*
! 	 	 * If the transfn is strict and the initval is NULL, make sure first
! 	 	 * input type and transtype are the same (or at least
! 	 	 * binary-compatible), so that it's OK to use the first input value as
! 	 	 * the initial transValue.
! 	 	 */
! 		if (proc->proisstrict && agginitval == NULL)
! 		{
! 			if (numArgs < 1 ||
! 				!IsBinaryCoercible(aggArgTypes[0], aggTransType))
! 				ereport(ERROR,
! 						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 						 errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
! 		}
! 		ReleaseSysCache(tup);
  	}
  
  	/* handle finalfn, if supplied */
! 	if (isOrderedSet)
! 	{
! 		int num_final_args = numArgs;
! 
! 		memcpy(fnArgs, aggArgTypes, num_final_args * sizeof(Oid));
! 
! 		/*
! 		 * If there's a transtype, it becomes the last arg to the finalfn;
! 		 * but if the agg (and hence the finalfn) is variadic "any", then
! 		 * this contributes nothing to the signature.
! 		 */
! 		if (aggTransType != InvalidOid && variadic_type != ANYOID)
! 			fnArgs[num_final_args++] = aggTransType;
! 
! 		finalfn = lookup_agg_function(aggfinalfnName, num_final_args, fnArgs,
! 									  &finaltype);
! 
! 		/*
! 		 * This is also checked at runtime for security reasons, but check
! 		 * here too to provide a friendly error (the requirement is because
! 		 * the finalfn will be passed null dummy args for type resolution
! 		 * purposes).
! 		 */
! 
! 		if (func_strict(finalfn))
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("ordered set final functions must not be declared STRICT")));
! 	}
! 	else if (aggfinalfnName)
  	{
  		fnArgs[0] = aggTransType;
  		finalfn = lookup_agg_function(aggfinalfnName, 1, fnArgs,
***************
*** 167,172 **** AggregateCreate(const char *aggName,
--- 348,354 ----
  		 */
  		finaltype = aggTransType;
  	}
+ 
  	Assert(OidIsValid(finaltype));
  
  	/*
***************
*** 208,213 **** AggregateCreate(const char *aggName,
--- 390,407 ----
  								false, -1);
  	}
  
+ 	/* handle transsortop, if supplied */
+ 	if (aggtranssortopName)
+ 	{
+ 		if (!isOrderedSet || !OidIsValid(aggTransType))
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 					 errmsg("transition sort operator can only be specified for ordered set functions with transition types")));
+ 		transsortop = LookupOperName(NULL, aggtranssortopName,
+ 									 aggTransType, aggTransType,
+ 									 false, -1);
+ 	}
+ 
  	/*
  	 * permission checks on used types
  	 */
***************
*** 218,232 **** AggregateCreate(const char *aggName,
  			aclcheck_error_type(aclresult, aggArgTypes[i]);
  	}
  
! 	aclresult = pg_type_aclcheck(aggTransType, GetUserId(), ACL_USAGE);
! 	if (aclresult != ACLCHECK_OK)
! 		aclcheck_error_type(aclresult, aggTransType);
  
  	aclresult = pg_type_aclcheck(finaltype, GetUserId(), ACL_USAGE);
  	if (aclresult != ACLCHECK_OK)
  		aclcheck_error_type(aclresult, finaltype);
  
- 
  	/*
  	 * Everything looks okay.  Try to create the pg_proc entry for the
  	 * aggregate.  (This could fail if there's already a conflicting entry.)
--- 412,428 ----
  			aclcheck_error_type(aclresult, aggArgTypes[i]);
  	}
  
! 	if (OidIsValid(aggTransType))
! 	{
! 		aclresult = pg_type_aclcheck(aggTransType, GetUserId(), ACL_USAGE);
! 		if (aclresult != ACLCHECK_OK)
! 			aclcheck_error_type(aclresult, aggTransType);
! 	}
  
  	aclresult = pg_type_aclcheck(finaltype, GetUserId(), ACL_USAGE);
  	if (aclresult != ACLCHECK_OK)
  		aclcheck_error_type(aclresult, finaltype);
  
  	/*
  	 * Everything looks okay.  Try to create the pg_proc entry for the
  	 * aggregate.  (This could fail if there's already a conflicting entry.)
***************
*** 247,253 **** AggregateCreate(const char *aggName,
  							  false,	/* security invoker (currently not
  										 * definable for agg) */
  							  false,	/* isLeakProof */
! 							  false,	/* isStrict (not needed for agg) */
  							  PROVOLATILE_IMMUTABLE,	/* volatility (not
  														 * needed for agg) */
  							  parameterTypes,	/* paramTypes */
--- 443,449 ----
  							  false,	/* security invoker (currently not
  										 * definable for agg) */
  							  false,	/* isLeakProof */
! 							  isStrict,	/* isStrict (needed for ordered set funcs) */
  							  PROVOLATILE_IMMUTABLE,	/* volatility (not
  														 * needed for agg) */
  							  parameterTypes,	/* paramTypes */
***************
*** 273,279 **** AggregateCreate(const char *aggName,
--- 469,478 ----
  	values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
  	values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
  	values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
+ 	values[Anum_pg_aggregate_aggtranssortop - 1] = ObjectIdGetDatum(transsortop);
  	values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
+ 	values[Anum_pg_aggregate_aggordnargs - 1] = Int32GetDatum(numDirectArgs);
+ 	values[Anum_pg_aggregate_aggisordsetfunc - 1] = BoolGetDatum(isOrderedSet);
  	values[Anum_pg_aggregate_aggtransspace - 1] = Int32GetDatum(aggTransSpace);
  	if (agginitval)
  		values[Anum_pg_aggregate_agginitval - 1] = CStringGetTextDatum(agginitval);
***************
*** 292,309 **** AggregateCreate(const char *aggName,
  
  	/*
  	 * Create dependencies for the aggregate (above and beyond those already
! 	 * made by ProcedureCreate).  Note: we don't need an explicit dependency
! 	 * on aggTransType since we depend on it indirectly through transfn.
  	 */
  	myself.classId = ProcedureRelationId;
  	myself.objectId = procOid;
  	myself.objectSubId = 0;
  
  	/* Depends on transition function */
! 	referenced.classId = ProcedureRelationId;
! 	referenced.objectId = transfn;
! 	referenced.objectSubId = 0;
! 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  
  	/* Depends on final function, if any */
  	if (OidIsValid(finalfn))
--- 491,513 ----
  
  	/*
  	 * Create dependencies for the aggregate (above and beyond those already
! 	 * made by ProcedureCreate).  Normal aggs don't need an explicit
! 	 * dependency on aggTransType since we depend on it indirectly through
! 	 * transfn, but ordered set functions with variadic "any" do need one
! 	 * (ordered set functions without variadic depend on it via the finalfn).
  	 */
  	myself.classId = ProcedureRelationId;
  	myself.objectId = procOid;
  	myself.objectSubId = 0;
  
  	/* Depends on transition function */
! 	if (OidIsValid(transfn))
! 	{
! 		referenced.classId = ProcedureRelationId;
! 		referenced.objectId = transfn;
! 		referenced.objectSubId = 0;
! 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
! 	}
  
  	/* Depends on final function, if any */
  	if (OidIsValid(finalfn))
***************
*** 323,328 **** AggregateCreate(const char *aggName,
--- 527,550 ----
  		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  	}
  
+ 	/* Depends on transsort operator, if any */
+ 	if (OidIsValid(transsortop))
+ 	{
+ 		referenced.classId = OperatorRelationId;
+ 		referenced.objectId = transsortop;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
+ 	/* May depend on aggTransType if any */
+ 	if (OidIsValid(aggTransType) && isOrderedSet && variadic_type == ANYOID)
+ 	{
+ 		referenced.classId = TypeRelationId;
+ 		referenced.objectId = aggTransType;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	return procOid;
  }
  
*** a/src/backend/commands/aggregatecmds.c
--- b/src/backend/commands/aggregatecmds.c
***************
*** 44,52 ****
   *	DefineAggregate
   *
   * "oldstyle" signals the old (pre-8.2) style where the aggregate input type
!  * is specified by a BASETYPE element in the parameters.  Otherwise,
!  * "args" is a list of FunctionParameter structs defining the agg's arguments.
!  * "parameters" is a list of DefElem representing the agg's definition clauses.
   */
  Oid
  DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
--- 44,55 ----
   *	DefineAggregate
   *
   * "oldstyle" signals the old (pre-8.2) style where the aggregate input type
!  * is specified by a BASETYPE element in the parameters.  Otherwise, "args" is
!  * a pair, whose first element is a list of FunctionParameter structs defining
!  * the agg's arguments (both direct and ordered), and whose second element is
!  * an Integer node with the number of direct args, or -1 if this isn't an
!  * ordered set func.  "parameters" is a list of DefElem representing the agg's
!  * definition clauses.
   */
  Oid
  DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
***************
*** 58,76 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  	List	   *transfuncName = NIL;
  	List	   *finalfuncName = NIL;
  	List	   *sortoperatorName = NIL;
  	TypeName   *baseType = NULL;
  	TypeName   *transType = NULL;
  	int32		transSpace = 0;
  	char	   *initval = NULL;
  	int			numArgs;
  	oidvector  *parameterTypes;
  	ArrayType  *allParameterTypes;
  	ArrayType  *parameterModes;
  	ArrayType  *parameterNames;
  	List	   *parameterDefaults;
- 	Oid			transTypeId;
  	char		transTypeType;
  	ListCell   *pl;
  
  	/* Convert list of names to a name and namespace */
  	aggNamespace = QualifiedNameGetCreationNamespace(name, &aggName);
--- 61,84 ----
  	List	   *transfuncName = NIL;
  	List	   *finalfuncName = NIL;
  	List	   *sortoperatorName = NIL;
+ 	List	   *transsortoperatorName = NIL;
  	TypeName   *baseType = NULL;
  	TypeName   *transType = NULL;
  	int32		transSpace = 0;
  	char	   *initval = NULL;
  	int			numArgs;
+ 	int			numDirectArgs = -1;
+ 	Oid			transTypeId = InvalidOid;
  	oidvector  *parameterTypes;
  	ArrayType  *allParameterTypes;
  	ArrayType  *parameterModes;
  	ArrayType  *parameterNames;
  	List	   *parameterDefaults;
  	char		transTypeType;
  	ListCell   *pl;
+ 	bool		ishypothetical = false;
+ 	bool		isOrderedSet = false;
+ 	bool		isStrict = false;
  
  	/* Convert list of names to a name and namespace */
  	aggNamespace = QualifiedNameGetCreationNamespace(name, &aggName);
***************
*** 81,86 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
--- 89,102 ----
  		aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
  					   get_namespace_name(aggNamespace));
  
+ 	Assert(args == NIL || list_length(args) == 2);
+ 
+ 	if (list_length(args) == 2)
+ 	{
+ 		numDirectArgs = intVal(lsecond(args));
+ 		isOrderedSet = (numDirectArgs != -1);
+ 	}
+ 
  	foreach(pl, parameters)
  	{
  		DefElem    *defel = (DefElem *) lfirst(pl);
***************
*** 109,114 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
--- 125,136 ----
  			initval = defGetString(defel);
  		else if (pg_strcasecmp(defel->defname, "initcond1") == 0)
  			initval = defGetString(defel);
+ 		else if (pg_strcasecmp(defel->defname, "hypothetical") == 0)
+ 			ishypothetical = true;
+ 		else if (pg_strcasecmp(defel->defname, "strict") == 0)
+ 			isStrict = true;
+ 		else if (pg_strcasecmp(defel->defname, "transsortop") == 0)
+ 			transsortoperatorName = defGetQualifiedName(defel);
  		else
  			ereport(WARNING,
  					(errcode(ERRCODE_SYNTAX_ERROR),
***************
*** 116,132 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  							defel->defname)));
  	}
  
! 	/*
! 	 * make sure we have our required definitions
! 	 */
! 	if (transType == NULL)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 				 errmsg("aggregate stype must be specified")));
! 	if (transfuncName == NIL)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 				 errmsg("aggregate sfunc must be specified")));
  
  	/*
  	 * look up the aggregate's input datatype(s).
--- 138,172 ----
  							defel->defname)));
  	}
  
! 	if (!isOrderedSet)
! 	{
! 		/*
! 		 * make sure we have our required definitions
! 		 */
! 		if (transType == NULL)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 						errmsg("aggregate stype must be specified")));
! 		if (transfuncName == NIL)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("aggregate sfunc must be specified")));
! 		if (isStrict)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("aggregate with sfunc must not be explicitly declared STRICT")));
! 	}
! 	else
! 	{
! 		if (transfuncName != NIL)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("sfunc must not be specified for ordered set functions")));
! 		if (finalfuncName == NIL)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("finalfunc must be specified for ordered set functions")));
! 	}
  
  	/*
  	 * look up the aggregate's input datatype(s).
***************
*** 176,183 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  					 errmsg("basetype is redundant with aggregate input type specification")));
  
! 		numArgs = list_length(args);
! 		interpret_function_parameter_list(args,
  										  InvalidOid,
  										  true, /* is an aggregate */
  										  queryString,
--- 216,230 ----
  					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  					 errmsg("basetype is redundant with aggregate input type specification")));
  
! 		/*
! 		 * The grammar has already concatenated the direct and ordered
! 		 * args (if any) for us. Note that error checking for position
! 		 * and number of VARIADIC args is not done for us, we have to
! 		 * do it ourselves later (in AggregateCreate)
! 		 */
! 
! 		numArgs = list_length(linitial(args));
! 		interpret_function_parameter_list(linitial(args),
  										  InvalidOid,
  										  true, /* is an aggregate */
  										  queryString,
***************
*** 194,200 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  	}
  
  	/*
! 	 * look up the aggregate's transtype.
  	 *
  	 * transtype can't be a pseudo-type, since we need to be able to store
  	 * values of the transtype.  However, we can allow polymorphic transtype
--- 241,247 ----
  	}
  
  	/*
! 	 * look up the aggregate's transtype, if specified.
  	 *
  	 * transtype can't be a pseudo-type, since we need to be able to store
  	 * values of the transtype.  However, we can allow polymorphic transtype
***************
*** 204,221 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  	 * worse) by connecting up incompatible internal-using functions in an
  	 * aggregate.
  	 */
! 	transTypeId = typenameTypeId(NULL, transType);
! 	transTypeType = get_typtype(transTypeId);
! 	if (transTypeType == TYPTYPE_PSEUDO &&
! 		!IsPolymorphicType(transTypeId))
  	{
! 		if (transTypeId == INTERNALOID && superuser())
! 			 /* okay */ ;
! 		else
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("aggregate transition data type cannot be %s",
! 							format_type_be(transTypeId))));
  	}
  
  	/*
--- 251,270 ----
  	 * worse) by connecting up incompatible internal-using functions in an
  	 * aggregate.
  	 */
! 	if (transType)
  	{
! 		transTypeId = typenameTypeId(NULL, transType);
! 		transTypeType = get_typtype(transTypeId);
! 		if (transTypeType == TYPTYPE_PSEUDO &&
! 			!IsPolymorphicType(transTypeId))
! 		{
! 			if (transTypeId != INTERNALOID || !superuser() || isOrderedSet)
! 				ereport(ERROR,
! 						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 						 errmsg("aggregate transition data type cannot be %s",
! 								format_type_be(transTypeId))));
! 		}
! 
  	}
  
  	/*
***************
*** 227,239 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  	 * value.  However, if it's an incorrect value it seems much more
  	 * user-friendly to complain at CREATE AGGREGATE time.
  	 */
! 	if (initval && transTypeType != TYPTYPE_PSEUDO)
  	{
! 		Oid			typinput,
! 					typioparam;
  
! 		getTypeInputInfo(transTypeId, &typinput, &typioparam);
! 		(void) OidInputFunctionCall(typinput, initval, typioparam, -1);
  	}
  
  	/*
--- 276,298 ----
  	 * value.  However, if it's an incorrect value it seems much more
  	 * user-friendly to complain at CREATE AGGREGATE time.
  	 */
! 	if (transType)
  	{
! 		if (initval && transTypeType != TYPTYPE_PSEUDO)
! 		{
! 			Oid			typinput,
! 						typioparam;
  
! 			getTypeInputInfo(transTypeId, &typinput, &typioparam);
! 			(void) OidInputFunctionCall(typinput, initval, typioparam, -1);
! 		}
! 	}
! 	else
! 	{
! 		if (initval)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					errmsg("INITVAL must not be specified without STYPE")));
  	}
  
  	/*
***************
*** 242,247 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
--- 301,307 ----
  	return AggregateCreate(aggName,		/* aggregate name */
  						   aggNamespace,		/* namespace */
  						   numArgs,
+ 						   numDirectArgs,
  						   parameterTypes,
  						   PointerGetDatum(allParameterTypes),
  						   PointerGetDatum(parameterModes),
***************
*** 250,256 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  						   transfuncName,		/* step function name */
  						   finalfuncName,		/* final function name */
  						   sortoperatorName,	/* sort operator name */
  						   transTypeId, /* transition data type */
  						   transSpace,	/* transition space */
! 						   initval);	/* initial condition */
  }
--- 310,320 ----
  						   transfuncName,		/* step function name */
  						   finalfuncName,		/* final function name */
  						   sortoperatorName,	/* sort operator name */
+ 						   transsortoperatorName,  /* transsort operator name */
  						   transTypeId, /* transition data type */
  						   transSpace,	/* transition space */
! 						   initval,  /* initial condition */
! 						   isStrict,  /* is explicitly STRICT */
! 						   isOrderedSet,  /* If the function is an ordered set */
! 						   ishypothetical);  /* If the function is a hypothetical set */
  }
*** a/src/backend/commands/functioncmds.c
--- b/src/backend/commands/functioncmds.c
***************
*** 274,281 **** interpret_function_parameter_list(List *parameters,
  		/* handle input parameters */
  		if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
  		{
! 			/* other input parameters can't follow a VARIADIC parameter */
! 			if (varCount > 0)
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  						 errmsg("VARIADIC parameter must be the last input parameter")));
--- 274,286 ----
  		/* handle input parameters */
  		if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
  		{
! 			/*
! 			 * For functions, other input parameters can't follow a VARIADIC
! 			 * parameter; for aggregates, we might be dealing with an ordered
! 			 * set function which have more complex rules for variadics, so
! 			 * punt the error checking for that case to the caller.
! 			 */
! 			if (varCount > 0 && !is_aggregate)
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  						 errmsg("VARIADIC parameter must be the last input parameter")));
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
***************
*** 4410,4415 **** ExecInitExpr(Expr *node, PlanState *parent)
--- 4410,4416 ----
  
  					astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
  														 parent);
+ 					astate->orddirectargs = (List *) ExecInitExpr((Expr *) aggref->orddirectargs, parent);
  					astate->aggfilter = ExecInitExpr(aggref->aggfilter,
  													 parent);
  
*** a/src/backend/executor/functions.c
--- b/src/backend/executor/functions.c
***************
*** 380,387 **** sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
  		param = ParseFuncOrColumn(pstate,
  								  list_make1(subfield),
  								  list_make1(param),
! 								  NIL, NULL, false, false, false,
! 								  NULL, true, cref->location);
  	}
  
  	return param;
--- 380,387 ----
  		param = ParseFuncOrColumn(pstate,
  								  list_make1(subfield),
  								  list_make1(param),
! 								  cref->location,
! 								  NULL);
  	}
  
  	return param;
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
***************
*** 3,9 ****
   * nodeAgg.c
   *	  Routines to handle aggregate nodes.
   *
!  *	  ExecAgg evaluates each aggregate in the following steps:
   *
   *		 transvalue = initcond
   *		 foreach input_tuple do
--- 3,9 ----
   * nodeAgg.c
   *	  Routines to handle aggregate nodes.
   *
!  *	  ExecAgg evaluates each normal aggregate in the following steps:
   *
   *		 transvalue = initcond
   *		 foreach input_tuple do
***************
*** 66,71 ****
--- 66,91 ----
   *	  AggState is available as context in earlier releases (back to 8.1),
   *	  but direct examination of the node is needed to use it before 9.0.
   *
+  *---
+  *
+  *    Ordered set functions modify the above process in a number of ways.
+  *    Most importantly, they do not have transfuncs at all; the same sort
+  *    mechanism used for ORDER BY/DISTINCT as described above is used to
+  *    process the input, but then the finalfunc is called without actually
+  *    running the sort (the finalfunc is allowed to insert rows first).
+  *    The finalfunc has access via a set of AggSet* API functions to the
+  *    Tuplesortstate, row count in the group, and other ancillary info.
+  *
+  *    Ordered set functions can, however, have a transvalue declared; this is
+  *    treated as a constant, and added to the end of the sort fields.
+  *    Hypothetical set functions use this to provide a flag that distinguishes
+  *    the hypothetical row from the input data.
+  *
+  *    Since they have no transfunc, ordered set functions have their own
+  *    'strict' flag stored in the aggregate's own pg_proc entry; this affects
+  *    whether rows containing nulls are placed in the sorter. But since we
+  *    pass dummy null arguments to the finalfunc for type resolution purposes,
+  *    no ordered set finalfunc is allowed to be strict.
   *
   * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
   * Portions Copyright (c) 1994, Regents of the University of California
***************
*** 87,96 ****
--- 107,118 ----
  #include "executor/nodeAgg.h"
  #include "miscadmin.h"
  #include "nodes/nodeFuncs.h"
+ #include "nodes/makefuncs.h"
  #include "optimizer/clauses.h"
  #include "optimizer/tlist.h"
  #include "parser/parse_agg.h"
  #include "parser/parse_coerce.h"
+ #include "parser/parse_clause.h"
  #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
***************
*** 105,110 ****
--- 127,134 ----
   */
  typedef struct AggStatePerAggData
  {
+ 	NodeTag type;
+ 	
  	/*
  	 * These values are set up during ExecInitAgg() and do not change
  	 * thereafter:
***************
*** 114,123 **** typedef struct AggStatePerAggData
  	AggrefExprState *aggrefstate;
  	Aggref	   *aggref;
  
! 	/* number of input arguments for aggregate function proper */
  	int			numArguments;
  
! 	/* number of inputs including ORDER BY expressions */
  	int			numInputs;
  
  	/* Oids of transfer functions */
--- 138,162 ----
  	AggrefExprState *aggrefstate;
  	Aggref	   *aggref;
  
! 	/* Pointer to parent AggState node */
! 	AggState   *aggstate;
! 
! 	/* copied from aggref */
! 	bool        isOrderedSet;
! 
! 	/*
! 	 * number of arguments for aggregate function proper.
! 	 * For ordered set functions, this includes the ORDER BY
! 	 * columns, *except* in the case of hypothetical set functions.
! 	 */
  	int			numArguments;
  
! 	/*
! 	 * number of inputs including ORDER BY expressions. For ordered
! 	 * set functions, *only* the ORDER BY expressions are included
! 	 * here, since the direct args to the function are not properly
! 	 * "input" in the sense of being derived from the tuple group.
! 	 */
  	int			numInputs;
  
  	/* Oids of transfer functions */
***************
*** 126,137 **** typedef struct AggStatePerAggData
  
  	/*
  	 * fmgr lookup data for transfer functions --- only valid when
! 	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
! 	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
  	FmgrInfo	finalfn;
  
  	/* Input collation derived for aggregate */
  	Oid			aggCollation;
  
--- 165,187 ----
  
  	/*
  	 * fmgr lookup data for transfer functions --- only valid when
! 	 * corresponding oid is not InvalidOid.
  	 */
  	FmgrInfo	transfn;
  	FmgrInfo	finalfn;
  
+ 	/*
+ 	 * If >0, aggregate as a whole is strict (skips null input)
+ 	 * The value specifies how many columns to check; normal aggs
+ 	 * only check numArguments, while ordered set functions check
+ 	 * numInputs.
+ 	 *
+ 	 * Ordered set functions are not allowed to have strict finalfns;
+ 	 * other aggregates respect the finalfn strict flag in the
+ 	 * FmgrInfo above.
+ 	 */
+ 	int         numStrict;
+ 
  	/* Input collation derived for aggregate */
  	Oid			aggCollation;
  
***************
*** 148,153 **** typedef struct AggStatePerAggData
--- 198,206 ----
  	Oid		   *sortCollations;
  	bool	   *sortNullsFirst;
  
+ 	/* just for convenience of ordered set funcs, not used here */
+ 	Oid		   *sortEqOperators;
+ 
  	/*
  	 * fmgr lookup data for input columns' equality operators --- only
  	 * set/used when aggregate has DISTINCT flag.  Note that these are in
***************
*** 204,209 **** typedef struct AggStatePerAggData
--- 257,265 ----
  	 */
  
  	Tuplesortstate *sortstate;	/* sort object, if DISTINCT or ORDER BY */
+ 
+ 	int64 number_of_rows;		/* number of rows */
+ 
  }	AggStatePerAggData;
  
  /*
***************
*** 300,305 **** initialize_aggregates(AggState *aggstate,
--- 356,363 ----
  		AggStatePerAgg peraggstate = &peragg[aggno];
  		AggStatePerGroup pergroupstate = &pergroup[aggno];
  
+ 		peraggstate->number_of_rows = 0;
+ 
  		/*
  		 * Start a fresh sort operation for each DISTINCT/ORDER BY aggregate.
  		 */
***************
*** 383,396 **** advance_transition_function(AggState *aggstate,
  	MemoryContext oldContext;
  	Datum		newVal;
  	int			i;
  
! 	if (peraggstate->transfn.fn_strict)
  	{
  		/*
  		 * For a strict transfn, nothing happens when there's a NULL input; we
  		 * just keep the prior transValue.
  		 */
! 		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
  				return;
--- 441,457 ----
  	MemoryContext oldContext;
  	Datum		newVal;
  	int			i;
+ 	int         numStrict = peraggstate->numStrict;
  
! 	Assert(OidIsValid(peraggstate->transfn_oid));
! 
! 	if (numStrict > 0)
  	{
  		/*
  		 * For a strict transfn, nothing happens when there's a NULL input; we
  		 * just keep the prior transValue.
  		 */
! 		for (i = 1; i <= numStrict; i++)
  		{
  			if (fcinfo->argnull[i])
  				return;
***************
*** 506,529 **** advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
  
  		if (peraggstate->numSortCols > 0)
  		{
  			/* DISTINCT and/or ORDER BY case */
  			Assert(slot->tts_nvalid == peraggstate->numInputs);
  
  			/*
! 			 * 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;
  			}
  
--- 567,590 ----
  
  		if (peraggstate->numSortCols > 0)
  		{
+ 			int numStrict = peraggstate->numStrict;
+ 
  			/* DISTINCT and/or ORDER BY case */
  			Assert(slot->tts_nvalid == peraggstate->numInputs);
  
  			/*
! 			 * If the aggregate 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.
  			 */
! 			if (numStrict > 0)
  			{
! 				for (i = 0; i < numStrict; i++)
  				{
  					if (slot->tts_isnull[i])
  						break;
  				}
! 				if (i < numStrict)
  					continue;
  			}
  
***************
*** 534,539 **** advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
--- 595,602 ----
  								   slot->tts_isnull[0]);
  			else
  				tuplesort_puttupleslot(peraggstate->sortstate, slot);
+ 
+ 			peraggstate->number_of_rows++;
  		}
  		else
  		{
***************
*** 756,770 **** finalize_aggregate(AggState *aggstate,
  	if (OidIsValid(peraggstate->finalfn_oid))
  	{
  		FunctionCallInfoData fcinfo;
  
! 		InitFunctionCallInfoData(fcinfo, &(peraggstate->finalfn), 1,
! 								 peraggstate->aggCollation,
! 								 (void *) aggstate, NULL);
! 		fcinfo.arg[0] = pergroupstate->transValue;
! 		fcinfo.argnull[0] = pergroupstate->transValueIsNull;
! 		if (fcinfo.flinfo->fn_strict && pergroupstate->transValueIsNull)
  		{
! 			/* don't call a strict function with NULL inputs */
  			*resultVal = (Datum) 0;
  			*resultIsNull = true;
  		}
--- 819,884 ----
  	if (OidIsValid(peraggstate->finalfn_oid))
  	{
  		FunctionCallInfoData fcinfo;
+ 		bool isnull = false;
+ 
+ 		if (!(peraggstate->isOrderedSet))
+ 		{
+ 			InitFunctionCallInfoData(fcinfo,
+ 									 &(peraggstate->finalfn),
+ 									 1,
+ 									 peraggstate->aggCollation,
+ 									 (void *) aggstate,
+ 									 NULL);
+ 
+ 			fcinfo.arg[0] = pergroupstate->transValue;
+ 			fcinfo.argnull[0] = isnull = pergroupstate->transValueIsNull;
+ 		}
+ 		else
+ 		{
+ 			List     *args = peraggstate->aggrefstate->orddirectargs;
+ 			ListCell *lc;
+ 			int       i = 0;
+ 			int       numArguments = peraggstate->numArguments;
+ 
+ 			ExecClearTuple(peraggstate->evalslot);
+ 			ExecClearTuple(peraggstate->uniqslot);
+ 
+ 			InitFunctionCallInfoData(fcinfo,
+ 									 &(peraggstate->finalfn),
+ 									 peraggstate->numArguments,
+ 									 peraggstate->aggCollation,
+ 									 (void *) peraggstate,
+ 									 NULL);
+ 
+ 			foreach (lc, args)
+ 			{
+ 				ExprState *expr = (ExprState *) lfirst(lc);
+ 
+ 				fcinfo.arg[i] = ExecEvalExpr(expr,
+ 											 aggstate->ss.ps.ps_ExprContext,
+ 											 &fcinfo.argnull[i],
+ 											 NULL);
+ 				if (fcinfo.argnull[i])
+ 					isnull = true;
  
! 				++i;
! 			}
! 
! 			for(; i < numArguments; i++)
! 			{
! 				fcinfo.arg[i] = (Datum) 0;
! 				fcinfo.argnull[i] = true;
! 				isnull = true;
! 			}
! 		}
! 
! 		if (isnull && fcinfo.flinfo->fn_strict)
  		{
! 			/*
! 			 * don't call a strict function with NULL inputs; for ordered set
! 			 * functions this is paranoia, we already required that fn_strict
! 			 * is false, but easy to check anyway
! 			 */
  			*resultVal = (Datum) 0;
  			*resultIsNull = true;
  		}
***************
*** 1164,1169 **** agg_retrieve_direct(AggState *aggstate)
--- 1278,1294 ----
  		}
  
  		/*
+ 		 * Use the representative input tuple for any references to
+ 		 * non-aggregated input columns in the qual and tlist.	(If we are not
+ 		 * grouping, and there are no input rows at all, we will come here
+ 		 * with an empty firstSlot ... but if not grouping, there can't be any
+ 		 * references to non-aggregated input columns, so no problem.)
+ 		 * We do this before finalizing because for ordered set functions,
+ 		 * finalize_aggregates can evaluate arguments referencing the tuple.
+ 		 */
+ 		econtext->ecxt_outertuple = firstSlot;
+ 
+ 		/*
  		 * Done scanning input tuple group. Finalize each aggregate
  		 * calculation, and stash results in the per-output-tuple context.
  		 */
***************
*** 1174,1187 **** agg_retrieve_direct(AggState *aggstate)
  
  			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,
--- 1299,1315 ----
  
  			if (peraggstate->numSortCols > 0)
  			{
! 				if (!(peraggstate->isOrderedSet))
! 				{
! 					if (peraggstate->numInputs == 1)
! 						process_ordered_aggregate_single(aggstate,
! 														 peraggstate,
! 														 pergroupstate);
! 					else
! 						process_ordered_aggregate_multi(aggstate,
! 														peraggstate,
! 														pergroupstate);
! 				}
  			}
  
  			finalize_aggregate(aggstate, peraggstate, pergroupstate,
***************
*** 1189,1203 **** agg_retrieve_direct(AggState *aggstate)
  		}
  
  		/*
- 		 * Use the representative input tuple for any references to
- 		 * non-aggregated input columns in the qual and tlist.	(If we are not
- 		 * grouping, and there are no input rows at all, we will come here
- 		 * with an empty firstSlot ... but if not grouping, there can't be any
- 		 * references to non-aggregated input columns, so no problem.)
- 		 */
- 		econtext->ecxt_outertuple = firstSlot;
- 
- 		/*
  		 * Check the qual (HAVING clause); if the group does not match, ignore
  		 * it and loop back to try to process another group.
  		 */
--- 1317,1322 ----
***************
*** 1568,1577 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
--- 1687,1698 ----
  		int			numInputs;
  		int			numSortCols;
  		int			numDistinctCols;
+ 		bool        isOrderedSet = aggref->isordset;
  		List	   *sortlist;
  		HeapTuple	aggTuple;
  		Form_pg_aggregate aggform;
  		Oid			aggtranstype;
+ 		Oid			aggtranstypecoll;
  		AclResult	aclresult;
  		Oid			transfn_oid,
  					finalfn_oid;
***************
*** 1580,1585 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
--- 1701,1710 ----
  		Datum		textInitVal;
  		int			i;
  		ListCell   *lc;
+         bool        is_strict;
+ 		Oid			inputCollations[FUNC_MAX_ARGS];
+ 		List       *argexprs;
+ 		List       *argexprstate;
  
  		/* Planner should have assigned aggregate to correct level */
  		Assert(aggref->agglevelsup == 0);
***************
*** 1601,1631 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  		/* Nope, so assign a new PerAgg record */
  		peraggstate = &peragg[++aggno];
  
- 		/* Mark Aggref state node with assigned index in the result array */
- 		aggrefstate->aggno = aggno;
- 
  		/* Fill in the peraggstate data */
! 		peraggstate->aggrefstate = aggrefstate;
  		peraggstate->aggref = aggref;
! 		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.
! 		 */
! 		numArguments = 0;
! 		foreach(lc, aggref->args)
! 		{
! 			TargetEntry *tle = (TargetEntry *) lfirst(lc);
  
! 			if (!tle->resjunk)
! 				inputTypes[numArguments++] = exprType((Node *) tle->expr);
! 		}
! 		peraggstate->numArguments = numArguments;
  
  		aggTuple = SearchSysCache1(AGGFNOID,
  								   ObjectIdGetDatum(aggref->aggfnoid));
  		if (!HeapTupleIsValid(aggTuple))
--- 1726,1743 ----
  		/* Nope, so assign a new PerAgg record */
  		peraggstate = &peragg[++aggno];
  
  		/* Fill in the peraggstate data */
! 		peraggstate->type = T_AggStatePerAggData;
! 		peraggstate->aggstate = aggstate;
  		peraggstate->aggref = aggref;
! 		peraggstate->aggrefstate = aggrefstate;
  
! 		peraggstate->isOrderedSet = isOrderedSet;
  
! 		/* Mark Aggref state node with assigned index in the result array */
! 		aggrefstate->aggno = aggno;
  
+ 		/* Fetch the pg_aggregate row */
  		aggTuple = SearchSysCache1(AGGFNOID,
  								   ObjectIdGetDatum(aggref->aggfnoid));
  		if (!HeapTupleIsValid(aggTuple))
***************
*** 1633,1638 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
--- 1745,1757 ----
  				 aggref->aggfnoid);
  		aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
  
+ 		/*
+ 		 * Check that the definition hasn't somehow changed incompatibly.
+ 		 */
+ 		if (isOrderedSet != (aggform->aggisordsetfunc)
+ 			|| (aggref->ishypothetical != (aggform->aggordnargs == -2)))
+ 			elog(ERROR, "incompatible change to aggregate definition");
+ 
  		/* Check permission to call aggregate function */
  		aclresult = pg_proc_aclcheck(aggref->aggfnoid, GetUserId(),
  									 ACL_EXECUTE);
***************
*** 1644,1668 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  		peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
  		peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
  
! 		/* Check that aggregate owner has permission to call component fns */
  		{
  			HeapTuple	procTuple;
  			Oid			aggOwner;
  
  			procTuple = SearchSysCache1(PROCOID,
  										ObjectIdGetDatum(aggref->aggfnoid));
  			if (!HeapTupleIsValid(procTuple))
  				elog(ERROR, "cache lookup failed for function %u",
  					 aggref->aggfnoid);
! 			aggOwner = ((Form_pg_proc) GETSTRUCT(procTuple))->proowner;
  			ReleaseSysCache(procTuple);
  
! 			aclresult = pg_proc_aclcheck(transfn_oid, aggOwner,
! 										 ACL_EXECUTE);
! 			if (aclresult != ACLCHECK_OK)
  				aclcheck_error(aclresult, ACL_KIND_PROC,
  							   get_func_name(transfn_oid));
! 			InvokeFunctionExecuteHook(transfn_oid);
  			if (OidIsValid(finalfn_oid))
  			{
  				aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
--- 1763,1799 ----
  		peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
  		peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
  
! 		/*
! 		 * Check that aggregate owner has permission to call component fns
! 		 * In passing, fetch the proisstrict flag for the aggregate proper,
! 		 * which subs for the transfn's strictness flag in cases where there
! 		 * is no transfn.
! 		 */
  		{
  			HeapTuple	procTuple;
  			Oid			aggOwner;
+ 			Form_pg_proc procp;
  
  			procTuple = SearchSysCache1(PROCOID,
  										ObjectIdGetDatum(aggref->aggfnoid));
  			if (!HeapTupleIsValid(procTuple))
  				elog(ERROR, "cache lookup failed for function %u",
  					 aggref->aggfnoid);
! 			procp = (Form_pg_proc) GETSTRUCT(procTuple);
! 			aggOwner = procp->proowner;
! 			is_strict = procp->proisstrict;
  			ReleaseSysCache(procTuple);
  
! 			if (OidIsValid(transfn_oid))
! 			{
! 				aclresult = pg_proc_aclcheck(transfn_oid, aggOwner,
! 									         ACL_EXECUTE);
! 				if (aclresult != ACLCHECK_OK && OidIsValid(transfn_oid))
  				aclcheck_error(aclresult, ACL_KIND_PROC,
  							   get_func_name(transfn_oid));
! 				InvokeFunctionExecuteHook(transfn_oid);
! 			}
! 
  			if (OidIsValid(finalfn_oid))
  			{
  				aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
***************
*** 1674,1690 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  			}
  		}
  
  		/* resolve actual type of transition state, if polymorphic */
  		aggtranstype = aggform->aggtranstype;
! 		if (IsPolymorphicType(aggtranstype))
  		{
  			/* have to fetch the agg's declared input types... */
  			Oid		   *declaredArgTypes;
! 			int			agg_nargs;
  
  			(void) get_func_signature(aggref->aggfnoid,
! 									  &declaredArgTypes, &agg_nargs);
! 			Assert(agg_nargs == numArguments);
  			aggtranstype = enforce_generic_type_consistency(inputTypes,
  															declaredArgTypes,
  															agg_nargs,
--- 1805,1841 ----
  			}
  		}
  
+ 		/*
+ 		 * 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.
+ 		 */
+ 
+ 		peraggstate->numInputs = numInputs = list_length(aggref->args);
+ 
+ 		numArguments = get_aggregate_argtypes(aggref,
+ 											  inputTypes,
+ 											  inputCollations);
+ 
  		/* resolve actual type of transition state, if polymorphic */
  		aggtranstype = aggform->aggtranstype;
! 		if (OidIsValid(aggtranstype) && IsPolymorphicType(aggtranstype))
  		{
  			/* have to fetch the agg's declared input types... */
  			Oid		   *declaredArgTypes;
! 			int         agg_nargs;
  
  			(void) get_func_signature(aggref->aggfnoid,
! 									  &declaredArgTypes,
! 									  &agg_nargs);
! 
! 			/*
! 			 * if variadic "any", might be more actual args than declared
! 			 * args, but these extra args can't influence the determination
! 			 * of polymorphic transition or result type.
! 			 */
! 			Assert(agg_nargs <= numArguments);
! 
  			aggtranstype = enforce_generic_type_consistency(inputTypes,
  															declaredArgTypes,
  															agg_nargs,
***************
*** 1693,1727 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  			pfree(declaredArgTypes);
  		}
  
  		/* build expression trees using actual argument & result types */
! 		build_aggregate_fnexprs(inputTypes,
! 								numArguments,
! 								aggref->aggvariadic,
! 								aggtranstype,
! 								aggref->aggtype,
! 								aggref->inputcollid,
! 								transfn_oid,
! 								finalfn_oid,
! 								&transfnexpr,
! 								&finalfnexpr);
! 
! 		fmgr_info(transfn_oid, &peraggstate->transfn);
! 		fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
  
  		if (OidIsValid(finalfn_oid))
  		{
  			fmgr_info(finalfn_oid, &peraggstate->finalfn);
  			fmgr_info_set_expr((Node *) finalfnexpr, &peraggstate->finalfn);
  		}
  
  		peraggstate->aggCollation = aggref->inputcollid;
  
  		get_typlenbyval(aggref->aggtype,
  						&peraggstate->resulttypeLen,
  						&peraggstate->resulttypeByVal);
! 		get_typlenbyval(aggtranstype,
! 						&peraggstate->transtypeLen,
! 						&peraggstate->transtypeByVal);
  
  		/*
  		 * initval is potentially null, so don't try to access it as a struct
--- 1844,1925 ----
  			pfree(declaredArgTypes);
  		}
  
+ 		aggtranstypecoll = get_typcollation(aggtranstype);
+ 
  		/* build expression trees using actual argument & result types */
! 
! 		if (!isOrderedSet)
! 		{
! 			build_aggregate_fnexprs(inputTypes,
! 									numArguments,
! 									aggref->aggvariadic,
! 									aggtranstype,
! 									aggref->aggtype,
! 									aggref->inputcollid,
! 									transfn_oid,
! 									finalfn_oid,
! 									&transfnexpr,
! 									&finalfnexpr);
! 		}
! 		else
! 		{
! 			/*
! 			 * The transvalue counts as an argument, but not for hypothetical
! 			 * set funcs.
! 			 */
! 			if (OidIsValid(aggtranstype) && !(aggref->ishypothetical))
! 			{
! 				if (numArguments == FUNC_MAX_ARGS)
! 					elog(ERROR, "too many arguments to ordered set function");
! 
! 				inputTypes[numArguments++] = aggtranstype;
! 				inputCollations[numArguments++] = aggtranstypecoll;
! 			}
! 
! 			build_orderedset_fnexprs(inputTypes,
! 									 numArguments,
! 									 aggref->aggvariadic,
! 									 aggref->aggtype,
! 									 aggref->inputcollid,
! 									 inputCollations,
! 									 finalfn_oid,
! 									 &finalfnexpr);
! 		}
! 
! 		peraggstate->numArguments = numArguments;
! 
! 		if (OidIsValid(transfn_oid))
! 		{
! 			fmgr_info(transfn_oid, &peraggstate->transfn);
! 			fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
! 
! 			is_strict = peraggstate->transfn.fn_strict;
! 		}
  
  		if (OidIsValid(finalfn_oid))
  		{
  			fmgr_info(finalfn_oid, &peraggstate->finalfn);
  			fmgr_info_set_expr((Node *) finalfnexpr, &peraggstate->finalfn);
+ 			if (peraggstate->finalfn.fn_strict && isOrderedSet)
+ 				elog(ERROR, "ordered set finalfns must not be strict");
  		}
  
+ 		if (is_strict)
+ 			peraggstate->numStrict = (isOrderedSet ? numInputs : numArguments);
+ 		else
+ 			peraggstate->numStrict = 0;
+ 
  		peraggstate->aggCollation = aggref->inputcollid;
  
  		get_typlenbyval(aggref->aggtype,
  						&peraggstate->resulttypeLen,
  						&peraggstate->resulttypeByVal);
! 		if (OidIsValid(aggtranstype))
! 		{
! 			get_typlenbyval(aggtranstype,
! 							&peraggstate->transtypeLen,
! 							&peraggstate->transtypeByVal);
! 		}
  
  		/*
  		 * initval is potentially null, so don't try to access it as a struct
***************
*** 1744,1750 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  		 * transValue.	This should have been checked at agg definition time,
  		 * but just in case...
  		 */
! 		if (peraggstate->transfn.fn_strict && peraggstate->initValueIsNull)
  		{
  			if (numArguments < 1 ||
  				!IsBinaryCoercible(inputTypes[0], aggtranstype))
--- 1942,1950 ----
  		 * transValue.	This should have been checked at agg definition time,
  		 * but just in case...
  		 */
! 		if (OidIsValid(peraggstate->transfn_oid)
! 			&& peraggstate->transfn.fn_strict
! 			&& peraggstate->initValueIsNull)
  		{
  			if (numArguments < 1 ||
  				!IsBinaryCoercible(inputTypes[0], aggtranstype))
***************
*** 1754,1774 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  								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
--- 1954,1961 ----
  								aggref->aggfnoid)));
  		}
  
! 		argexprs = aggref->args;
! 		argexprstate = aggrefstate->args;
  
  		/*
  		 * If we're doing either DISTINCT or ORDER BY, then we have a list of
***************
*** 1777,1782 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
--- 1964,1974 ----
  		 *
  		 * Note that by construction, if there is a DISTINCT clause then the
  		 * ORDER BY clause is a prefix of it (see transformDistinctClause).
+ 		 *
+ 		 * If we're doing an ordered set function, though, we want to do the
+ 		 * initialization for DISTINCT since the ordered set finalfn might
+ 		 * want it, and it's much easier to do it here. So set numDistinctCols
+ 		 * and let the later initialization take care of it.
  		 */
  		if (aggref->aggdistinct)
  		{
***************
*** 1788,1798 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  		{
  			sortlist = aggref->aggorder;
  			numSortCols = list_length(sortlist);
! 			numDistinctCols = 0;
  		}
  
  		peraggstate->numSortCols = numSortCols;
  		peraggstate->numDistinctCols = numDistinctCols;
  
  		if (numSortCols > 0)
  		{
--- 1980,2065 ----
  		{
  			sortlist = aggref->aggorder;
  			numSortCols = list_length(sortlist);
! 			numDistinctCols = isOrderedSet ? numSortCols : 0;
! 		}
! 
! 		/*
! 		 * If this is an ordered set function, and we have a transtype, then
! 		 * it represents an extra column to be added to the sorter with a
! 		 * fixed value. Plus, if aggtranssortop is valid, we have to include
! 		 * a sort entry for the new column.
! 		 *
! 		 * I'd probably have done this in the planner if I'd seen any
! 		 * possible place to put it; if there is one, it's very obscure.
! 		 */
! 
! 		if (OidIsValid(aggtranstype) && isOrderedSet)
! 		{
! 			Oid sortop = aggform->aggtranssortop;
! 			Const *node = makeNode(Const);
! 			TargetEntry *tle;
! 			SortGroupClause *sortcl = NULL;
! 
! 			node->consttype = aggtranstype;
! 			node->consttypmod = -1;
! 			node->constcollid = aggtranstypecoll;
! 			node->constlen = peraggstate->transtypeLen;
! 			node->constvalue = peraggstate->initValue;
! 			node->constisnull = peraggstate->initValueIsNull;
! 			node->constbyval = peraggstate->transtypeByVal;
! 			node->location = -1;
! 
! 			tle = makeTargetEntry((Expr *) node,
! 								  ++numInputs,
! 								  NULL,
! 								  true);
! 
! 			peraggstate->numInputs = numInputs;
! 
! 			if (OidIsValid(sortop))
! 			{
! 				Assert(aggref->aggdistinct == NIL);
! 
! 				sortcl = makeNode(SortGroupClause);
! 
! 				sortcl->tleSortGroupRef = assignSortGroupRef(tle, argexprs);
! 
! 				sortcl->sortop = sortop;
! 				sortcl->hashable = false;
! 				sortcl->eqop = get_equality_op_for_ordering_op(sortop,
! 															   &sortcl->nulls_first);
! 
! 				sortlist = lappend(list_copy(sortlist), sortcl);
! 				++numSortCols;
! 				++numDistinctCols;
! 			}
! 
! 			/* shallow-copy the passed-in lists, which we must not scribble on. */
! 
! 			argexprs = lappend(list_copy(argexprs), (Node *) tle);
! 			argexprstate = lappend(list_copy(argexprstate),
! 								   ExecInitExpr((Expr *) tle, (PlanState *) aggstate));
  		}
  
  		peraggstate->numSortCols = numSortCols;
  		peraggstate->numDistinctCols = numDistinctCols;
+ 		peraggstate->sortstate = NULL;
+ 
+ 		/*
+ 		 * Get a tupledesc corresponding to the inputs (including sort
+ 		 * expressions) of the agg.
+ 		 */
+ 		peraggstate->evaldesc = ExecTypeFromTL(argexprs, 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(argexprstate,
+ 														aggstate->tmpcontext,
+ 														peraggstate->evalslot,
+ 														NULL);
  
  		if (numSortCols > 0)
  		{
***************
*** 1805,1815 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  			/* 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);
--- 2072,2083 ----
  			/* If we have only one input, we need its len/byval info. */
  			if (numInputs == 1)
  			{
! 				get_typlenbyval(peraggstate->evaldesc->attrs[0]->atttypid,
  								&peraggstate->inputtypeLen,
  								&peraggstate->inputtypeByVal);
  			}
! 
! 			if (numDistinctCols > 0 && (numInputs > 1 || isOrderedSet))
  			{
  				/* we will need an extra slot to store prior values */
  				peraggstate->uniqslot = ExecInitExtraTupleSlot(estate);
***************
*** 1822,1871 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  				(AttrNumber *) palloc(numSortCols * sizeof(AttrNumber));
  			peraggstate->sortOperators =
  				(Oid *) palloc(numSortCols * sizeof(Oid));
  			peraggstate->sortCollations =
  				(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->sortColIdx[i] = tle->resno;
  				peraggstate->sortOperators[i] = sortcl->sortop;
  				peraggstate->sortCollations[i] = exprCollation((Node *) tle->expr);
  				peraggstate->sortNullsFirst[i] = sortcl->nulls_first;
- 				i++;
- 			}
- 			Assert(i == numSortCols);
- 		}
  
! 		if (aggref->aggdistinct)
! 		{
! 			Assert(numArguments > 0);
! 
! 			/*
! 			 * We need the equal function for each DISTINCT comparison we will
! 			 * make.
! 			 */
! 			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);
--- 2090,2136 ----
  				(AttrNumber *) palloc(numSortCols * sizeof(AttrNumber));
  			peraggstate->sortOperators =
  				(Oid *) palloc(numSortCols * sizeof(Oid));
+ 			peraggstate->sortEqOperators =
+ 				(Oid *) palloc(numSortCols * sizeof(Oid));
  			peraggstate->sortCollations =
  				(Oid *) palloc(numSortCols * sizeof(Oid));
  			peraggstate->sortNullsFirst =
  				(bool *) palloc(numSortCols * sizeof(bool));
+ 			if (numDistinctCols > 0)
+ 				peraggstate->equalfns =
+ 					(FmgrInfo *) palloc(numDistinctCols * sizeof(FmgrInfo));
+ 			else
+ 				peraggstate->equalfns = NULL;
+ 			
  
  			i = 0;
  			foreach(lc, sortlist)
  			{
  				SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
  				TargetEntry *tle = get_sortgroupclause_tle(sortcl,
! 														   argexprs);
  
  				/* the parser should have made sure of this */
  				Assert(OidIsValid(sortcl->sortop));
  
  				peraggstate->sortColIdx[i] = tle->resno;
  				peraggstate->sortOperators[i] = sortcl->sortop;
+ 				peraggstate->sortEqOperators[i] = sortcl->eqop;
  				peraggstate->sortCollations[i] = exprCollation((Node *) tle->expr);
  				peraggstate->sortNullsFirst[i] = sortcl->nulls_first;
  
! 				/*
! 				 * It's OK to get the equalfns here too, since we already
! 				 * require that sortlist is aggref->aggdistinct for the normal
! 				 * distinct case, and for ordered set functions using the
! 				 * (possibly modified copy of) aggref->aggorder is correct
! 				 */
! 				if (peraggstate->equalfns)
! 					fmgr_info(get_opcode(sortcl->eqop), &peraggstate->equalfns[i]);
  
  				i++;
  			}
! 			Assert(i == numSortCols);
  		}
  
  		ReleaseSysCache(aggTuple);
***************
*** 2023,2028 **** ExecReScanAgg(AggState *node)
--- 2288,2296 ----
   * If aggcontext isn't NULL, the function also stores at *aggcontext the
   * identity of the memory context that aggregate transition values are
   * being stored in.
+  *
+  * We do NOT include AGG_CONTEXT_ORDERED as a possible return here, since
+  * that would open a security hole.
   */
  int
  AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
***************
*** 2063,2065 **** aggregate_dummy(PG_FUNCTION_ARGS)
--- 2331,2448 ----
  		 fcinfo->flinfo->fn_oid);
  	return (Datum) 0;			/* keep compiler quiet */
  }
+ 
+ /* AggSetGetRowCount - Get the number of rows in case of ordered set
+  * functions.
+  */
+ int64
+ AggSetGetRowCount(FunctionCallInfo fcinfo)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ 	{
+ 		return ((AggStatePerAggData *)fcinfo->context)->number_of_rows;
+ 	}
+ 
+ 	elog(ERROR, "Called AggSetGetRowCount on non ordered set function");
+ 	return -1;
+ }
+ 
+ /* AggSetGetSortInfo - Get the sort state in the case of 
+  * ordered set functions.
+  */
+ void
+ AggSetGetSortInfo(FunctionCallInfo fcinfo,
+ 				  Tuplesortstate **sortstate,
+ 				  TupleDesc *tupdesc,
+ 				  TupleTableSlot **tupslot,
+ 				  Oid *datumtype)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ 	{
+ 		AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+ 
+ 		*sortstate = peraggstate->sortstate;
+ 		if (peraggstate->numInputs == 1)
+ 		{
+ 			if (tupdesc)
+ 				*tupdesc = NULL;
+ 			if (datumtype)
+ 				*datumtype = peraggstate->evaldesc->attrs[0]->atttypid;
+ 		}
+ 		else
+ 		{
+ 			if (tupdesc)
+ 				*tupdesc = peraggstate->evaldesc;
+ 			if (datumtype)
+ 				*datumtype = InvalidOid;
+ 		}
+ 		
+ 		if (tupslot)
+ 			*tupslot = peraggstate->evalslot;
+ 	}
+ 	else
+ 		elog(ERROR, "AggSetSortInfo called on non ordered set function");
+ }
+ 
+ int
+ AggSetGetDistinctInfo(FunctionCallInfo fcinfo,
+ 					  TupleTableSlot **uniqslot,
+ 					  AttrNumber **sortColIdx,
+ 					  FmgrInfo **equalfns)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ 	{
+ 		AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+ 
+ 		if (uniqslot)
+ 			*uniqslot = peraggstate->uniqslot;
+ 		if (sortColIdx)
+ 			*sortColIdx = peraggstate->sortColIdx;
+ 		if (equalfns)
+ 			*equalfns = peraggstate->equalfns;
+ 
+ 		return peraggstate->numDistinctCols;
+ 	}
+ 	else
+ 		elog(ERROR, "AggSetGetDistinctOperators called on non ordered set function");
+ }
+ 
+ int
+ AggSetGetSortOperators(FunctionCallInfo fcinfo,
+ 					   AttrNumber **sortColIdx,
+ 					   Oid **sortOperators,
+ 					   Oid **sortEqOperators,
+ 					   Oid **sortCollations,
+ 					   bool **sortNullsFirst)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ 	{
+ 		AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+ 
+ 		if (sortColIdx)
+ 			*sortColIdx = peraggstate->sortColIdx;
+ 		if (sortOperators)
+ 			*sortOperators = peraggstate->sortOperators;
+ 		if (sortEqOperators)
+ 			*sortEqOperators = peraggstate->sortEqOperators;
+ 		if (sortCollations)
+ 			*sortCollations = peraggstate->sortCollations;
+ 		if (sortNullsFirst)
+ 			*sortNullsFirst = peraggstate->sortNullsFirst;
+ 
+ 		return peraggstate->numSortCols;
+ 	}
+ 	else
+ 		elog(ERROR, "AggSetGetSortOperators called on non ordered set function");
+ }
+ 
+ void
+ AggSetGetPerTupleContext(FunctionCallInfo fcinfo,
+ 						 MemoryContext *memcontext)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ 	{
+ 		AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+ 		*memcontext = peraggstate->aggstate->tmpcontext->ecxt_per_tuple_memory;
+ 	}
+ }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1139,1147 **** _copyAggref(const Aggref *from)
--- 1139,1150 ----
  	COPY_NODE_FIELD(args);
  	COPY_NODE_FIELD(aggorder);
  	COPY_NODE_FIELD(aggdistinct);
+ 	COPY_NODE_FIELD(orddirectargs);
  	COPY_NODE_FIELD(aggfilter);
  	COPY_SCALAR_FIELD(aggstar);
  	COPY_SCALAR_FIELD(aggvariadic);
+ 	COPY_SCALAR_FIELD(isordset);
+ 	COPY_SCALAR_FIELD(ishypothetical);
  	COPY_SCALAR_FIELD(agglevelsup);
  	COPY_LOCATION_FIELD(location);
  
***************
*** 2174,2179 **** _copyFuncCall(const FuncCall *from)
--- 2177,2183 ----
  	COPY_SCALAR_FIELD(agg_star);
  	COPY_SCALAR_FIELD(agg_distinct);
  	COPY_SCALAR_FIELD(func_variadic);
+ 	COPY_SCALAR_FIELD(has_within_group);
  	COPY_NODE_FIELD(over);
  	COPY_LOCATION_FIELD(location);
  
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 196,204 **** _equalAggref(const Aggref *a, const Aggref *b)
--- 196,207 ----
  	COMPARE_NODE_FIELD(args);
  	COMPARE_NODE_FIELD(aggorder);
  	COMPARE_NODE_FIELD(aggdistinct);
+ 	COMPARE_NODE_FIELD(orddirectargs);
  	COMPARE_NODE_FIELD(aggfilter);
  	COMPARE_SCALAR_FIELD(aggstar);
  	COMPARE_SCALAR_FIELD(aggvariadic);
+ 	COMPARE_SCALAR_FIELD(isordset);
+ 	COMPARE_SCALAR_FIELD(ishypothetical);
  	COMPARE_SCALAR_FIELD(agglevelsup);
  	COMPARE_LOCATION_FIELD(location);
  
***************
*** 2015,2020 **** _equalFuncCall(const FuncCall *a, const FuncCall *b)
--- 2018,2024 ----
  	COMPARE_SCALAR_FIELD(agg_star);
  	COMPARE_SCALAR_FIELD(agg_distinct);
  	COMPARE_SCALAR_FIELD(func_variadic);
+ 	COMPARE_SCALAR_FIELD(has_within_group);
  	COMPARE_NODE_FIELD(over);
  	COMPARE_LOCATION_FIELD(location);
  
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
***************
*** 558,563 **** makeFuncCall(List *name, List *args, int location)
--- 558,564 ----
  	n->agg_star = FALSE;
  	n->agg_distinct = FALSE;
  	n->func_variadic = FALSE;
+ 	n->has_within_group = FALSE;
  	n->over = NULL;
  	return n;
  }
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 1631,1636 **** expression_tree_walker(Node *node,
--- 1631,1641 ----
  				if (expression_tree_walker((Node *) expr->aggdistinct,
  										   walker, context))
  					return true;
+ 
+ 				if (expression_tree_walker((Node *) expr->orddirectargs,
+ 										   walker, context))
+ 					return true;
+ 
  				if (walker((Node *) expr->aggfilter, context))
  					return true;
  			}
***************
*** 2155,2161 **** expression_tree_mutator(Node *node,
--- 2160,2168 ----
  				MUTATE(newnode->args, aggref->args, List *);
  				MUTATE(newnode->aggorder, aggref->aggorder, List *);
  				MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
+ 				MUTATE(newnode->orddirectargs, aggref->orddirectargs, List *);
  				MUTATE(newnode->aggfilter, aggref->aggfilter, Expr *);
+ 
  				return (Node *) newnode;
  			}
  			break;
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 960,968 **** _outAggref(StringInfo str, const Aggref *node)
--- 960,971 ----
  	WRITE_NODE_FIELD(args);
  	WRITE_NODE_FIELD(aggorder);
  	WRITE_NODE_FIELD(aggdistinct);
+ 	WRITE_NODE_FIELD(orddirectargs);
  	WRITE_NODE_FIELD(aggfilter);
  	WRITE_BOOL_FIELD(aggstar);
  	WRITE_BOOL_FIELD(aggvariadic);
+ 	WRITE_BOOL_FIELD(isordset);
+ 	WRITE_BOOL_FIELD(ishypothetical);
  	WRITE_UINT_FIELD(agglevelsup);
  	WRITE_LOCATION_FIELD(location);
  }
***************
*** 2091,2096 **** _outFuncCall(StringInfo str, const FuncCall *node)
--- 2094,2100 ----
  	WRITE_BOOL_FIELD(agg_star);
  	WRITE_BOOL_FIELD(agg_distinct);
  	WRITE_BOOL_FIELD(func_variadic);
+ 	WRITE_BOOL_FIELD(has_within_group);
  	WRITE_NODE_FIELD(over);
  	WRITE_LOCATION_FIELD(location);
  }
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 495,503 **** _readAggref(void)
--- 495,506 ----
  	READ_NODE_FIELD(args);
  	READ_NODE_FIELD(aggorder);
  	READ_NODE_FIELD(aggdistinct);
+ 	READ_NODE_FIELD(orddirectargs);
  	READ_NODE_FIELD(aggfilter);
  	READ_BOOL_FIELD(aggstar);
  	READ_BOOL_FIELD(aggvariadic);
+ 	READ_BOOL_FIELD(isordset);
+ 	READ_BOOL_FIELD(ishypothetical);
  	READ_UINT_FIELD(agglevelsup);
  	READ_LOCATION_FIELD(location);
  
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
***************
*** 39,44 ****
--- 39,45 ----
  #include "parser/analyze.h"
  #include "parser/parse_coerce.h"
  #include "parser/parse_func.h"
+ #include "parser/parse_agg.h"
  #include "rewrite/rewriteManip.h"
  #include "tcop/tcopprot.h"
  #include "utils/acl.h"
***************
*** 465,471 **** count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
  		QualCost	argcosts;
  		Oid		   *inputTypes;
  		int			numArguments;
- 		ListCell   *l;
  
  		Assert(aggref->agglevelsup == 0);
  
--- 466,471 ----
***************
*** 488,494 **** count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
  			costs->numOrderedAggs++;
  
  		/* add component function execution costs to appropriate totals */
! 		costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost;
  		if (OidIsValid(aggfinalfn))
  			costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost;
  
--- 488,495 ----
  			costs->numOrderedAggs++;
  
  		/* add component function execution costs to appropriate totals */
! 		if (OidIsValid(aggtransfn))
! 			costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost;
  		if (OidIsValid(aggfinalfn))
  			costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost;
  
***************
*** 506,589 **** count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
  		costs->transCost.startup += argcosts.startup;
  		costs->transCost.per_tuple += argcosts.per_tuple;
  
- 		/* extract argument types (ignoring any ORDER BY expressions) */
- 		inputTypes = (Oid *) palloc(sizeof(Oid) * list_length(aggref->args));
- 		numArguments = 0;
- 		foreach(l, aggref->args)
- 		{
- 			TargetEntry *tle = (TargetEntry *) lfirst(l);
- 
- 			if (!tle->resjunk)
- 				inputTypes[numArguments++] = exprType((Node *) tle->expr);
- 		}
- 
- 		/* resolve actual type of transition state, if polymorphic */
- 		if (IsPolymorphicType(aggtranstype))
- 		{
- 			/* have to fetch the agg's declared input types... */
- 			Oid		   *declaredArgTypes;
- 			int			agg_nargs;
- 
- 			(void) get_func_signature(aggref->aggfnoid,
- 									  &declaredArgTypes, &agg_nargs);
- 			Assert(agg_nargs == numArguments);
- 			aggtranstype = enforce_generic_type_consistency(inputTypes,
- 															declaredArgTypes,
- 															agg_nargs,
- 															aggtranstype,
- 															false);
- 			pfree(declaredArgTypes);
- 		}
- 
  		/*
! 		 * If the transition type is pass-by-value then it doesn't add
! 		 * anything to the required size of the hashtable.	If it is
! 		 * pass-by-reference then we have to add the estimated size of the
! 		 * value itself, plus palloc overhead.
  		 */
! 		if (!get_typbyval(aggtranstype))
  		{
! 			int32		avgwidth;
  
! 			/* Use average width if aggregate definition gave one */
! 			if (aggtransspace > 0)
! 				avgwidth = aggtransspace;
! 			else
  			{
  				/*
! 				 * If transition state is of same type as first input, assume
! 				 * it's the same typmod (same width) as well.  This works for
! 				 * cases like MAX/MIN and is probably somewhat reasonable
! 				 * otherwise.
  				 */
! 				int32		aggtranstypmod;
  
! 				if (numArguments > 0 && aggtranstype == inputTypes[0])
! 					aggtranstypmod = exprTypmod((Node *) linitial(aggref->args));
  				else
! 					aggtranstypmod = -1;
  
! 				avgwidth = get_typavgwidth(aggtranstype, aggtranstypmod);
  			}
  
! 			avgwidth = MAXALIGN(avgwidth);
! 			costs->transitionSpace += avgwidth + 2 * sizeof(void *);
  		}
! 		else if (aggtranstype == INTERNALOID)
  		{
! 			/*
! 			 * INTERNAL transition type is a special case: although INTERNAL
! 			 * is pass-by-value, it's almost certainly being used as a pointer
! 			 * to some large data structure.  The aggregate definition can
! 			 * provide an estimate of the size.  If it doesn't, then we assume
! 			 * ALLOCSET_DEFAULT_INITSIZE, which is a good guess if the data is
! 			 * being kept in a private memory context, as is done by
! 			 * array_agg() for instance.
! 			 */
! 			if (aggtransspace > 0)
! 				costs->transitionSpace += aggtransspace;
! 			else
! 				costs->transitionSpace += ALLOCSET_DEFAULT_INITSIZE;
  		}
  
  		/*
--- 507,610 ----
  		costs->transCost.startup += argcosts.startup;
  		costs->transCost.per_tuple += argcosts.per_tuple;
  
  		/*
! 		 * If we're doing a sorted agg, we can punt the entire
! 		 * determination of transition element size since we're not
! 		 * going to be using it to determine hashtable limits. This
! 		 * simplifies the code for hypothetical set functions.
  		 */
! 
! 		if (aggref->aggorder == NIL && aggref->aggdistinct == NIL)
  		{
! 			Assert(!aggref->isordset);
  
! 			/* extract argument types (ignoring any ORDER BY expressions) */
! 			inputTypes = (Oid *) palloc(sizeof(Oid) * FUNC_MAX_ARGS);
! 
! 			numArguments = get_aggregate_argtypes(aggref, inputTypes, NULL);
! 
! 			/* resolve actual type of transition state, if polymorphic */
! 			if (OidIsValid(aggtranstype) && IsPolymorphicType(aggtranstype))
  			{
+ 				/* have to fetch the agg's declared input types... */
+ 				Oid		   *declaredArgTypes;
+ 				int			agg_nargs;
+ 
+ 				(void) get_func_signature(aggref->aggfnoid,
+ 										  &declaredArgTypes, &agg_nargs);
+ 
  				/*
! 				 * if variadic "any", might be more actual args than declared
! 				 * args, but these extra args can't influence the determination
! 				 * of polymorphic transition or result type.
  				 */
! 				Assert(agg_nargs <= numArguments);
! 
! 				aggtranstype = enforce_generic_type_consistency(inputTypes,
! 																declaredArgTypes,
! 																agg_nargs,
! 																aggtranstype,
! 																false);
! 				pfree(declaredArgTypes);
! 			}
! 
! 			/*
! 			 * If the transition type is pass-by-value then it doesn't add
! 			 * anything to the required size of the hashtable.	If it is
! 			 * pass-by-reference then we have to add the estimated size of the
! 			 * value itself, plus palloc overhead.
! 			 */
! 			if (OidIsValid(aggtranstype) && !get_typbyval(aggtranstype))
! 			{
! 				int32		avgwidth;
  
! 				/* Use average width if aggregate definition gave one */
! 				if (aggtransspace > 0)
! 					avgwidth = aggtransspace;
  				else
! 				{
! 					/*
! 					 * If transition state is of same type as first input, assume
! 					 * it's the same typmod (same width) as well.  This works for
! 					 * cases like MAX/MIN and is probably somewhat reasonable
! 					 * otherwise.
! 					 */
! 					int32		aggtranstypmod;
! 
! 					if (numArguments > 0 && aggtranstype == inputTypes[0])
! 						aggtranstypmod = exprTypmod((Node *) linitial(aggref->args));
! 					else
! 						aggtranstypmod = -1;
  
! 					avgwidth = get_typavgwidth(aggtranstype, aggtranstypmod);
! 				}
! 
! 				avgwidth = MAXALIGN(avgwidth);
! 
! 				costs->transitionSpace += avgwidth + 2 * sizeof(void *);
! 			}
! 			else if (aggtranstype == INTERNALOID)
! 			{
! 				/*
! 				 * INTERNAL transition type is a special case: although
! 				 * INTERNAL is pass-by-value, it's almost certainly being used
! 				 * as a pointer to some large data structure.  The aggregate
! 				 * definition can provide an estimate of the size.  If it
! 				 * doesn't, then we assume ALLOCSET_DEFAULT_INITSIZE, which is
! 				 * a good guess if the data is being kept in a private memory
! 				 * context, as is done by array_agg() for instance.
! 				 */
! 				if (aggtransspace > 0)
! 					costs->transitionSpace += aggtransspace;
! 				else
! 					costs->transitionSpace += ALLOCSET_DEFAULT_INITSIZE;
  			}
  
! 			pfree(inputTypes);
  		}
! 		else
  		{
! 			costs->transitionSpace = work_mem;   /* just in case */
  		}
  
  		/*
***************
*** 3911,3917 **** recheck_cast_function_args(List *args, Oid result_type, HeapTuple func_tuple)
  		elog(ERROR, "function's resolved result type changed during planning");
  
  	/* perform any necessary typecasting of arguments */
! 	make_fn_arguments(NULL, args, actual_arg_types, declared_arg_types);
  }
  
  /*
--- 3932,3938 ----
  		elog(ERROR, "function's resolved result type changed during planning");
  
  	/* perform any necessary typecasting of arguments */
! 	make_fn_arguments(NULL, args, NULL, actual_arg_types, declared_arg_types, false);
  }
  
  /*
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 951,957 **** transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
  										  &qry->targetList,
  										  EXPR_KIND_ORDER_BY,
  										  true /* fix unknowns */ ,
! 										  false /* allow SQL92 rules */ );
  
  	qry->groupClause = transformGroupClause(pstate,
  											stmt->groupClause,
--- 951,958 ----
  										  &qry->targetList,
  										  EXPR_KIND_ORDER_BY,
  										  true /* fix unknowns */ ,
! 										  false /* allow SQL92 rules */,
! 										  false /* don't add duplicates */);
  
  	qry->groupClause = transformGroupClause(pstate,
  											stmt->groupClause,
***************
*** 1211,1217 **** transformValuesClause(ParseState *pstate, SelectStmt *stmt)
  										  &qry->targetList,
  										  EXPR_KIND_ORDER_BY,
  										  true /* fix unknowns */ ,
! 										  false /* allow SQL92 rules */ );
  
  	qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
  											EXPR_KIND_OFFSET, "OFFSET");
--- 1212,1219 ----
  										  &qry->targetList,
  										  EXPR_KIND_ORDER_BY,
  										  true /* fix unknowns */ ,
! 										  false /* allow SQL92 rules */,
! 										  false /* don't add duplicates */ );
  
  	qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
  											EXPR_KIND_OFFSET, "OFFSET");
***************
*** 1435,1441 **** transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
  										  &qry->targetList,
  										  EXPR_KIND_ORDER_BY,
  										  false /* no unknowns expected */ ,
! 										  false /* allow SQL92 rules */ );
  
  	/* restore namespace, remove jrte from rtable */
  	pstate->p_namespace = sv_namespace;
--- 1437,1444 ----
  										  &qry->targetList,
  										  EXPR_KIND_ORDER_BY,
  										  false /* no unknowns expected */ ,
! 										  false /* allow SQL92 rules */ ,
! 										  false /* don't add duplicates */ );
  
  	/* restore namespace, remove jrte from rtable */
  	pstate->p_namespace = sv_namespace;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 495,500 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
--- 495,501 ----
  %type <str>		opt_existing_window_name
  %type <boolean> opt_if_not_exists
  %type <node>    filter_clause
+ %type <list> 	within_group_clause
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
***************
*** 597,603 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
  	VERBOSE VERSION_P VIEW VOLATILE
  
! 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
  
  	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
  	XMLPI XMLROOT XMLSERIALIZE
--- 598,604 ----
  	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
  	VERBOSE VERSION_P VIEW VOLATILE
  
! 	WHEN WHERE WITHIN WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
  
  	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
  	XMLPI XMLROOT XMLSERIALIZE
***************
*** 3704,3710 **** AlterExtensionContentsStmt:
  					n->action = $4;
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $6;
! 					n->objargs = extractArgTypes($7);
  					$$ = (Node *)n;
  				}
  			| ALTER EXTENSION name add_drop CAST '(' Typename AS Typename ')'
--- 3705,3711 ----
  					n->action = $4;
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $6;
! 					n->objargs = extractArgTypes(linitial($7));
  					$$ = (Node *)n;
  				}
  			| ALTER EXTENSION name add_drop CAST '(' Typename AS Typename ')'
***************
*** 5283,5289 **** CommentStmt:
  					CommentStmt *n = makeNode(CommentStmt);
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $4;
! 					n->objargs = extractArgTypes($5);
  					n->comment = $7;
  					$$ = (Node *) n;
  				}
--- 5284,5290 ----
  					CommentStmt *n = makeNode(CommentStmt);
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $4;
! 					n->objargs = extractArgTypes(linitial($5));
  					n->comment = $7;
  					$$ = (Node *) n;
  				}
***************
*** 5449,5455 **** SecLabelStmt:
  					n->provider = $3;
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $6;
! 					n->objargs = extractArgTypes($7);
  					n->label = $9;
  					$$ = (Node *) n;
  				}
--- 5450,5456 ----
  					n->provider = $3;
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $6;
! 					n->objargs = extractArgTypes(linitial($7));
  					n->label = $9;
  					$$ = (Node *) n;
  				}
***************
*** 6449,6457 **** aggr_arg:	func_arg
  				}
  		;
  
! /* Zero-argument aggregates are named with * for consistency with COUNT(*) */
! aggr_args:	'(' aggr_args_list ')'					{ $$ = $2; }
! 			| '(' '*' ')'							{ $$ = NIL; }
  		;
  
  aggr_args_list:
--- 6450,6502 ----
  				}
  		;
  
! /*
!  * Aggregate args (for create aggregate, etc.) are treated as follows:
!  *
!  * (*)                              - no args
!  * (func_arg,func_arg,...)          - normal agg with args
!  * () within group (func_arg,...)   - ordered set func with no direct args
!  * (func_arg,...) within group (func_arg,...)  - ordered set func with args
!  * (func_arg,...) within group (*)  - ordered set func variadic special case
!  *
!  * This doesn't correspond to anything in the spec because the spec doesn't
!  * have any DDL to create or modify ordered set functions, so we're winging
!  * it here.
!  *
!  * Almost everything we do with an ordered set function treats its arguments
!  * as though they were a single list, with the direct and grouped arg types
!  * concatenated. So for simplicity, we construct a single list here.
!  *
!  * But we still need to know when creating an agg (but not for referring to it
!  * later) where the division between direct and ordered args is; so this
!  * production returns a pair (arglist,num) where num is the number of direct
!  * args, or -1 if no within group clause was used. Most users of aggr_args,
!  * other than CREATE AGGREGATE, therefore only need to pay attention to
!  * linitial($n).
!  */
! 
! aggr_args:  '(' '*' ')'
! 				{
! 					$$ = list_make2(NIL, makeInteger(-1));
! 				}
! 			| '(' aggr_args_list ')'
! 				{
! 					$$ = list_make2($2, makeInteger(-1));
! 				}
! 			| '(' ')' WITHIN GROUP_P '(' aggr_args_list ')'
! 				{
! 					$$ = list_make2($6, makeInteger(0));
! 				}
! 			| '(' aggr_args_list ')' WITHIN GROUP_P '(' aggr_args_list ')'
! 				{
! 					int n = list_length($2);
! 					$$ = list_make2(list_concat($2,$7), makeInteger(n));
! 				}
! 			| '(' aggr_args_list ')' WITHIN GROUP_P '(' '*' ')'
! 				{
! 					int n = list_length($2);
! 					$$ = list_make2($2, makeInteger(n));
! 				}
  		;
  
  aggr_args_list:
***************
*** 6657,6663 **** RemoveAggrStmt:
  					DropStmt *n = makeNode(DropStmt);
  					n->removeType = OBJECT_AGGREGATE;
  					n->objects = list_make1($3);
! 					n->arguments = list_make1(extractArgTypes($4));
  					n->behavior = $5;
  					n->missing_ok = false;
  					n->concurrent = false;
--- 6702,6708 ----
  					DropStmt *n = makeNode(DropStmt);
  					n->removeType = OBJECT_AGGREGATE;
  					n->objects = list_make1($3);
! 					n->arguments = list_make1(extractArgTypes(linitial($4)));
  					n->behavior = $5;
  					n->missing_ok = false;
  					n->concurrent = false;
***************
*** 6668,6674 **** RemoveAggrStmt:
  					DropStmt *n = makeNode(DropStmt);
  					n->removeType = OBJECT_AGGREGATE;
  					n->objects = list_make1($5);
! 					n->arguments = list_make1(extractArgTypes($6));
  					n->behavior = $7;
  					n->missing_ok = true;
  					n->concurrent = false;
--- 6713,6719 ----
  					DropStmt *n = makeNode(DropStmt);
  					n->removeType = OBJECT_AGGREGATE;
  					n->objects = list_make1($5);
! 					n->arguments = list_make1(extractArgTypes(linitial($6)));
  					n->behavior = $7;
  					n->missing_ok = true;
  					n->concurrent = false;
***************
*** 6884,6890 **** RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
  					RenameStmt *n = makeNode(RenameStmt);
  					n->renameType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes($4);
  					n->newname = $7;
  					n->missing_ok = false;
  					$$ = (Node *)n;
--- 6929,6935 ----
  					RenameStmt *n = makeNode(RenameStmt);
  					n->renameType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes(linitial($4));
  					n->newname = $7;
  					n->missing_ok = false;
  					$$ = (Node *)n;
***************
*** 7358,7364 **** AlterObjectSchemaStmt:
  					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
  					n->objectType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes($4);
  					n->newschema = $7;
  					n->missing_ok = false;
  					$$ = (Node *)n;
--- 7403,7409 ----
  					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
  					n->objectType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes(linitial($4));
  					n->newschema = $7;
  					n->missing_ok = false;
  					$$ = (Node *)n;
***************
*** 7587,7593 **** AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
  					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
  					n->objectType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes($4);
  					n->newowner = $7;
  					$$ = (Node *)n;
  				}
--- 7632,7638 ----
  					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
  					n->objectType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes(linitial($4));
  					n->newowner = $7;
  					$$ = (Node *)n;
  				}
***************
*** 9475,9480 **** sortby:		a_expr USING qual_all_Op opt_nulls_order
--- 9520,9530 ----
  		;
  
  
+ within_group_clause:
+ 			WITHIN GROUP_P '(' sort_clause ')'  { $$ = $4; }
+ 			| /*EMPTY*/                         { $$ = NIL; }
+          ;
+ 
  select_limit:
  			limit_clause offset_clause			{ $$ = list_make2($2, $1); }
  			| offset_clause limit_clause		{ $$ = list_make2($1, $2); }
***************
*** 11180,11191 **** func_application: func_name '(' ')'
   * (Note that many of the special SQL functions wouldn't actually make any
   * sense as functional index entries, but we ignore that consideration here.)
   */
! func_expr: func_application filter_clause over_clause
  				{
! 					FuncCall *n = (FuncCall*)$1;
! 					n->agg_filter = $2;
! 					n->over = $3;
! 					$$ = (Node*)n;
  				}
  			| func_expr_common_subexpr
  				{ $$ = $1; }
--- 11230,11264 ----
   * (Note that many of the special SQL functions wouldn't actually make any
   * sense as functional index entries, but we ignore that consideration here.)
   */
! func_expr: func_application within_group_clause filter_clause over_clause
  				{
! 					FuncCall *n = (FuncCall *) $1;
! 					/*
! 					 * the order clause for WITHIN GROUP and the one
! 					 * for aggregate ORDER BY share a field, so we
! 					 * have to check here that at most one is present.
! 					 * We check for DISTINCT here to give a better
! 					 * error position.	Other consistency checks are
! 					 * deferred to parse_func.c or parse_agg.c
! 					 */
! 					if ($2 != NIL)
! 					{
! 						if (n->agg_order != NIL)
! 							ereport(ERROR,
! 									(errcode(ERRCODE_SYNTAX_ERROR),
! 									 errmsg("cannot have multiple ORDER BY clauses with WITHIN GROUP"),
! 									 parser_errposition(@2)));
! 						if (n->agg_distinct)
! 							ereport(ERROR,
! 									(errcode(ERRCODE_SYNTAX_ERROR),
! 									 errmsg("cannot have DISTINCT and WITHIN GROUP together"),
! 									 parser_errposition(@2)));
! 						n->agg_order = $2;
! 						n->has_within_group = TRUE;
! 					}
! 					n->agg_filter = $3;
! 					n->over = $4;
! 					$$ = (Node *) n;
  				}
  			| func_expr_common_subexpr
  				{ $$ = $1; }
***************
*** 12744,12749 **** unreserved_keyword:
--- 12817,12823 ----
  			| VIEW
  			| VOLATILE
  			| WHITESPACE_P
+ 			| WITHIN
  			| WITHOUT
  			| WORK
  			| WRAPPER
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
***************
*** 44,50 **** typedef struct
  	int			sublevels_up;
  } check_ungrouped_columns_context;
  
! static int	check_agg_arguments(ParseState *pstate, List *args, Expr *filter);
  static bool check_agg_arguments_walker(Node *node,
  						   check_agg_arguments_context *context);
  static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
--- 44,52 ----
  	int			sublevels_up;
  } check_ungrouped_columns_context;
  
! static int	check_agg_arguments(ParseState *pstate, 
! 							List *args, 
! 							List *agg_ordset, Expr *filter);
  static bool check_agg_arguments_walker(Node *node,
  						   check_agg_arguments_context *context);
  static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
***************
*** 75,81 **** static bool check_ungrouped_columns_walker(Node *node,
   */
  void
  transformAggregateCall(ParseState *pstate, Aggref *agg,
! 					   List *args, List *aggorder, bool agg_distinct)
  {
  	List	   *tlist;
  	List	   *torder;
--- 77,84 ----
   */
  void
  transformAggregateCall(ParseState *pstate, Aggref *agg,
! 					   List *args, List *aggorder,
! 					   bool agg_distinct, bool agg_within_group)
  {
  	List	   *tlist;
  	List	   *torder;
***************
*** 93,104 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
  	 */
  	tlist = NIL;
  	attno = 1;
! 	foreach(lc, args)
  	{
! 		Expr	   *arg = (Expr *) lfirst(lc);
! 		TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false);
  
! 		tlist = lappend(tlist, tle);
  	}
  
  	/*
--- 96,119 ----
  	 */
  	tlist = NIL;
  	attno = 1;
! 
! 	if (agg_within_group)
  	{
! 		agg->isordset = TRUE;
! 		agg->orddirectargs = args;
! 	}
! 	else
! 	{
! 		foreach(lc, args)
! 		{
! 			Expr	   *arg = (Expr *) lfirst(lc);
! 			TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false);
  
! 			tlist = lappend(tlist, tle);
! 		}
! 
! 		agg->isordset = FALSE;
! 		agg->orddirectargs = NIL;
  	}
  
  	/*
***************
*** 109,114 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
--- 124,134 ----
  	 *
  	 * We need to mess with p_next_resno since it will be used to number any
  	 * new targetlist entries.
+ 	 *
+ 	 * If and only if we're doing a WITHIN GROUP list, we preserve any
+ 	 * duplicate expressions in the sort clause. This is needed because the
+ 	 * sort clause of WITHIN GROUP is really an argument list, and we must
+ 	 * keep the number and content of entries matching the specified input.
  	 */
  	save_next_resno = pstate->p_next_resno;
  	pstate->p_next_resno = attno;
***************
*** 118,124 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
  								 &tlist,
  								 EXPR_KIND_ORDER_BY,
  								 true /* fix unknowns */ ,
! 								 true /* force SQL99 rules */ );
  
  	/*
  	 * If we have DISTINCT, transform that to produce a distinctList.
--- 138,145 ----
  								 &tlist,
  								 EXPR_KIND_ORDER_BY,
  								 true /* fix unknowns */ ,
! 								 true /* force SQL99 rules */ ,
! 								 agg_within_group /* keep duplicates? */ );
  
  	/*
  	 * If we have DISTINCT, transform that to produce a distinctList.
***************
*** 160,166 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
  	 * Check the arguments to compute the aggregate's level and detect
  	 * improper nesting.
  	 */
! 	min_varlevel = check_agg_arguments(pstate, agg->args, agg->aggfilter);
  	agg->agglevelsup = min_varlevel;
  
  	/* Mark the correct pstate level as having aggregates */
--- 181,188 ----
  	 * Check the arguments to compute the aggregate's level and detect
  	 * improper nesting.
  	 */
! 	min_varlevel = check_agg_arguments(pstate, 
! 										agg->args, agg->orddirectargs, agg->aggfilter);
  	agg->agglevelsup = min_varlevel;
  
  	/* Mark the correct pstate level as having aggregates */
***************
*** 312,318 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
   * which we can't know until we finish scanning the arguments.
   */
  static int
! check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
  {
  	int			agglevel;
  	check_agg_arguments_context context;
--- 334,340 ----
   * which we can't know until we finish scanning the arguments.
   */
  static int
! check_agg_arguments(ParseState *pstate, List *args, List *agg_ordset, Expr *filter)
  {
  	int			agglevel;
  	check_agg_arguments_context context;
***************
*** 330,335 **** check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
--- 352,360 ----
  								  check_agg_arguments_walker,
  								  (void *) &context);
  
+ 	(void) expression_tree_walker((Node *) agg_ordset,	  check_agg_arguments_walker,
+ 								  (void *) &context);
+ 
  	/*
  	 * If we found no vars nor aggs at all, it's a level-zero aggregate;
  	 * otherwise, its level is the minimum of vars or aggs.
***************
*** 353,360 **** check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
  				(errcode(ERRCODE_GROUPING_ERROR),
  				 errmsg("aggregate function calls cannot be nested"),
  				 parser_errposition(pstate,
! 									locate_agg_of_level((Node *) args,
! 														agglevel))));
  
  	return agglevel;
  }
--- 378,385 ----
  				(errcode(ERRCODE_GROUPING_ERROR),
  				 errmsg("aggregate function calls cannot be nested"),
  				 parser_errposition(pstate,
! 								locate_agg_of_level((Node *) args,
! 								agglevel))));
  
  	return agglevel;
  }
***************
*** 823,830 **** check_ungrouped_columns_walker(Node *node,
  	 * We do need to look at aggregates of lower levels, however.
  	 */
  	if (IsA(node, Aggref) &&
! 		(int) ((Aggref *) node)->agglevelsup >= context->sublevels_up)
  		return false;
  
  	/*
  	 * If we have any GROUP BY items that are not simple Vars, check to see if
--- 848,863 ----
  	 * We do need to look at aggregates of lower levels, however.
  	 */
  	if (IsA(node, Aggref) &&
! 		(int) ((Aggref *) node)->agglevelsup > context->sublevels_up)
! 	{
  		return false;
+ 	}
+ 	else if (IsA(node, Aggref) &&
+ 			(int) ((Aggref *) node)->agglevelsup == context->sublevels_up)
+ 	{
+ 		return check_ungrouped_columns_walker((Node*)(((Aggref *)node)->orddirectargs), 
+ 														context);
+ 	}
  
  	/*
  	 * If we have any GROUP BY items that are not simple Vars, check to see if
***************
*** 1042,1044 **** build_aggregate_fnexprs(Oid *agg_input_types,
--- 1075,1172 ----
  										 agg_input_collation,
  										 COERCE_EXPLICIT_CALL);
  }
+ 
+ void
+ build_orderedset_fnexprs(Oid *agg_input_types,
+ 						 int agg_num_inputs,
+ 						 bool agg_variadic,
+ 						 Oid agg_result_type,
+ 						 Oid agg_input_collation,
+ 						 Oid *agg_input_collation_array,
+ 						 Oid finalfn_oid,
+ 						 Expr **finalfnexpr)
+ {
+ 	FuncExpr   *fexpr;
+ 	Param	   *argp;
+ 	List	   *args = NIL;
+ 	int			i = 0;
+ 
+ 	/*
+ 	 * Build arg list to use in the finalfn FuncExpr node. We really only care
+ 	 * that finalfn can discover the actual argument types at runtime using
+ 	 * get_fn_expr_argtype(), so it's okay to use Param nodes that don't
+ 	 * correspond to any real Param.
+ 	 */
+ 	for (i = 0; i < agg_num_inputs; i++)
+ 	{
+ 		argp = makeNode(Param);
+ 		argp->paramkind = PARAM_EXEC;
+ 		argp->paramid = -1;
+ 		argp->paramtype = agg_input_types[i];
+ 		argp->paramtypmod = -1;
+ 		argp->paramcollid = agg_input_collation_array[i];
+ 		argp->location = -1;
+ 
+ 		args = lappend(args, argp);
+ 	}
+ 
+ 	fexpr = makeFuncExpr(finalfn_oid,
+ 						 agg_result_type,
+ 						 args,
+ 						 InvalidOid,
+ 						 agg_input_collation,
+ 						 COERCE_EXPLICIT_CALL);
+ 	fexpr->funcvariadic = agg_variadic;
+ 	*finalfnexpr = (Expr *) fexpr;
+ }
+ 
+ int get_aggregate_argtypes(Aggref *aggref, Oid *inputTypes, Oid *inputCollations)
+ {
+ 	int numArguments = 0;
+ 	ListCell   *lc;
+ 
+ 	if (!(aggref->isordset))
+ 	{
+ 		foreach(lc, aggref->args)
+ 		{
+ 			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ 
+ 			if (!tle->resjunk)
+ 			{
+ 				inputTypes[numArguments] = exprType((Node *) tle->expr);
+ 				if (inputCollations != NULL)
+ 					inputCollations[numArguments] = exprCollation((Node *) tle->expr);
+ 				++numArguments;
+ 			}
+ 		}
+ 	}
+ 	else
+ 	{
+ 		foreach(lc, aggref->orddirectargs)
+ 		{
+ 			Node *expr_orddirectargs = lfirst(lc);
+ 
+ 			inputTypes[numArguments] = exprType(expr_orddirectargs);
+ 			if (inputCollations != NULL)
+ 				inputCollations[numArguments] = exprCollation(expr_orddirectargs);
+ 
+ 			++numArguments;
+ 		}
+ 
+ 		if (!(aggref->ishypothetical))
+ 		{
+ 			foreach(lc, aggref->args)
+ 			{
+ 				TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ 
+ 				inputTypes[numArguments] = exprType((Node *) tle->expr);
+ 				if (inputCollations != NULL)
+ 					inputCollations[numArguments] = exprCollation((Node *) tle->expr);
+ 
+ 				++numArguments;
+ 			}
+ 		}
+ 	}
+ 
+ 	return numArguments;
+ }
*** a/src/backend/parser/parse_clause.c
--- b/src/backend/parser/parse_clause.c
***************
*** 71,77 **** static void checkExprIsVarFree(ParseState *pstate, Node *n,
  static TargetEntry *findTargetlistEntrySQL92(ParseState *pstate, Node *node,
  						 List **tlist, ParseExprKind exprKind);
  static TargetEntry *findTargetlistEntrySQL99(ParseState *pstate, Node *node,
! 						 List **tlist, ParseExprKind exprKind);
  static int get_matching_location(int sortgroupref,
  					  List *sortgrouprefs, List *exprs);
  static List *addTargetToSortList(ParseState *pstate, TargetEntry *tle,
--- 71,78 ----
  static TargetEntry *findTargetlistEntrySQL92(ParseState *pstate, Node *node,
  						 List **tlist, ParseExprKind exprKind);
  static TargetEntry *findTargetlistEntrySQL99(ParseState *pstate, Node *node,
! 											 List **tlist, ParseExprKind exprKind,
! 											 bool keepDuplicates);
  static int get_matching_location(int sortgroupref,
  					  List *sortgrouprefs, List *exprs);
  static List *addTargetToSortList(ParseState *pstate, TargetEntry *tle,
***************
*** 1477,1483 **** findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
  	/*
  	 * Otherwise, we have an expression, so process it per SQL99 rules.
  	 */
! 	return findTargetlistEntrySQL99(pstate, node, tlist, exprKind);
  }
  
  /*
--- 1478,1484 ----
  	/*
  	 * Otherwise, we have an expression, so process it per SQL99 rules.
  	 */
! 	return findTargetlistEntrySQL99(pstate, node, tlist, exprKind, false);
  }
  
  /*
***************
*** 1492,1501 **** findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
   * node		the ORDER BY, GROUP BY, etc expression to be matched
   * tlist	the target list (passed by reference so we can append to it)
   * exprKind identifies clause type being processed
   */
  static TargetEntry *
  findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
! 						 ParseExprKind exprKind)
  {
  	TargetEntry *target_result;
  	ListCell   *tl;
--- 1493,1503 ----
   * node		the ORDER BY, GROUP BY, etc expression to be matched
   * tlist	the target list (passed by reference so we can append to it)
   * exprKind identifies clause type being processed
+  * keepDuplicates  if true, don't try and match to any existing entry
   */
  static TargetEntry *
  findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
! 						 ParseExprKind exprKind, bool keepDuplicates)
  {
  	TargetEntry *target_result;
  	ListCell   *tl;
***************
*** 1510,1533 **** findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
  	 */
  	expr = transformExpr(pstate, node, exprKind);
  
! 	foreach(tl, *tlist)
  	{
! 		TargetEntry *tle = (TargetEntry *) lfirst(tl);
! 		Node	   *texpr;
  
! 		/*
! 		 * Ignore any implicit cast on the existing tlist expression.
! 		 *
! 		 * This essentially allows the ORDER/GROUP/etc item to adopt the same
! 		 * datatype previously selected for a textually-equivalent tlist item.
! 		 * There can't be any implicit cast at top level in an ordinary SELECT
! 		 * tlist at this stage, but the case does arise with ORDER BY in an
! 		 * aggregate function.
! 		 */
! 		texpr = strip_implicit_coercions((Node *) tle->expr);
  
! 		if (equal(expr, texpr))
! 			return tle;
  	}
  
  	/*
--- 1512,1538 ----
  	 */
  	expr = transformExpr(pstate, node, exprKind);
  
! 	if (!keepDuplicates)
  	{
! 		foreach(tl, *tlist)
! 		{
! 			TargetEntry *tle = (TargetEntry *) lfirst(tl);
! 			Node	   *texpr;
  
! 			/*
! 			 * Ignore any implicit cast on the existing tlist expression.
! 			 *
! 			 * This essentially allows the ORDER/GROUP/etc item to adopt the same
! 			 * datatype previously selected for a textually-equivalent tlist item.
! 			 * There can't be any implicit cast at top level in an ordinary SELECT
! 			 * tlist at this stage, but the case does arise with ORDER BY in an
! 			 * aggregate function.
! 			 */
! 			texpr = strip_implicit_coercions((Node *) tle->expr);
  
! 			if (equal(expr, texpr))
! 				return tle;
! 		}
  	}
  
  	/*
***************
*** 1569,1575 **** transformGroupClause(ParseState *pstate, List *grouplist,
  
  		if (useSQL99)
  			tle = findTargetlistEntrySQL99(pstate, gexpr,
! 										   targetlist, exprKind);
  		else
  			tle = findTargetlistEntrySQL92(pstate, gexpr,
  										   targetlist, exprKind);
--- 1574,1580 ----
  
  		if (useSQL99)
  			tle = findTargetlistEntrySQL99(pstate, gexpr,
! 										   targetlist, exprKind, false);
  		else
  			tle = findTargetlistEntrySQL92(pstate, gexpr,
  										   targetlist, exprKind);
***************
*** 1636,1646 **** transformSortClause(ParseState *pstate,
  					List **targetlist,
  					ParseExprKind exprKind,
  					bool resolveUnknown,
! 					bool useSQL99)
  {
  	List	   *sortlist = NIL;
  	ListCell   *olitem;
  
  	foreach(olitem, orderlist)
  	{
  		SortBy	   *sortby = (SortBy *) lfirst(olitem);
--- 1641,1654 ----
  					List **targetlist,
  					ParseExprKind exprKind,
  					bool resolveUnknown,
! 					bool useSQL99,
! 	                bool keepDuplicates)
  {
  	List	   *sortlist = NIL;
  	ListCell   *olitem;
  
+ 	Assert(useSQL99 || !keepDuplicates);
+ 
  	foreach(olitem, orderlist)
  	{
  		SortBy	   *sortby = (SortBy *) lfirst(olitem);
***************
*** 1648,1654 **** transformSortClause(ParseState *pstate,
  
  		if (useSQL99)
  			tle = findTargetlistEntrySQL99(pstate, sortby->node,
! 										   targetlist, exprKind);
  		else
  			tle = findTargetlistEntrySQL92(pstate, sortby->node,
  										   targetlist, exprKind);
--- 1656,1662 ----
  
  		if (useSQL99)
  			tle = findTargetlistEntrySQL99(pstate, sortby->node,
! 										   targetlist, exprKind, keepDuplicates);
  		else
  			tle = findTargetlistEntrySQL92(pstate, sortby->node,
  										   targetlist, exprKind);
***************
*** 1718,1724 **** transformWindowDefinitions(ParseState *pstate,
  										  targetlist,
  										  EXPR_KIND_WINDOW_ORDER,
  										  true /* fix unknowns */ ,
! 										  true /* force SQL99 rules */ );
  		partitionClause = transformGroupClause(pstate,
  											   windef->partitionClause,
  											   targetlist,
--- 1726,1733 ----
  										  targetlist,
  										  EXPR_KIND_WINDOW_ORDER,
  										  true /* fix unknowns */ ,
! 										  true /* force SQL99 rules */,
! 			                              false /* don't add duplicates */);
  		partitionClause = transformGroupClause(pstate,
  											   windef->partitionClause,
  											   targetlist,
*** a/src/backend/parser/parse_collate.c
--- b/src/backend/parser/parse_collate.c
***************
*** 73,79 **** typedef struct
  static bool assign_query_collations_walker(Node *node, ParseState *pstate);
  static bool assign_collations_walker(Node *node,
  						 assign_collations_context *context);
! 
  
  /*
   * assign_query_collations()
--- 73,81 ----
  static bool assign_query_collations_walker(Node *node, ParseState *pstate);
  static bool assign_collations_walker(Node *node,
  						 assign_collations_context *context);
! static void assign_aggregate_collations(Aggref *aggref,
! 						assign_collations_context *context,
! 						assign_collations_context *loccontext);
  
  /*
   * assign_query_collations()
***************
*** 564,607 **** assign_collations_walker(Node *node, assign_collations_context *context)
  					case T_Aggref:
  						{
  							/*
! 							 * Aggref is a special case because expressions
! 							 * used only for ordering shouldn't be taken to
! 							 * conflict with each other or with regular args.
! 							 * So we apply assign_expr_collations() to them
! 							 * rather than passing down our loccontext.
! 							 *
! 							 * Note that we recurse to each TargetEntry, not
! 							 * directly to its contained expression, so that
! 							 * the case above for T_TargetEntry will apply
! 							 * appropriate checks to agg ORDER BY items.
! 							 *
! 							 * Likewise, we assign collations for the (bool)
! 							 * expression in aggfilter, independently of any
! 							 * other args.
! 							 *
! 							 * We need not recurse into the aggorder or
! 							 * aggdistinct lists, because those contain only
! 							 * SortGroupClause nodes which we need not
! 							 * process.
  							 */
  							Aggref	   *aggref = (Aggref *) node;
- 							ListCell   *lc;
  
! 							foreach(lc, aggref->args)
! 							{
! 								TargetEntry *tle = (TargetEntry *) lfirst(lc);
! 
! 								Assert(IsA(tle, TargetEntry));
! 								if (tle->resjunk)
! 									assign_expr_collations(context->pstate,
! 														   (Node *) tle);
! 								else
! 									(void) assign_collations_walker((Node *) tle,
! 																&loccontext);
! 							}
  
  							assign_expr_collations(context->pstate,
! 												 (Node *) aggref->aggfilter);
  						}
  						break;
  					case T_WindowFunc:
--- 566,581 ----
  					case T_Aggref:
  						{
  							/*
! 							 * Aggref is special enough that we give it its own
! 							 * function. The FILTER clause is independent of the
! 							 * rest of the aggregate, however.
  							 */
  							Aggref	   *aggref = (Aggref *) node;
  
! 							assign_aggregate_collations(aggref, context, &loccontext);
  
  							assign_expr_collations(context->pstate,
! 												   (Node *) aggref->aggfilter);
  						}
  						break;
  					case T_WindowFunc:
***************
*** 802,804 **** assign_collations_walker(Node *node, assign_collations_context *context)
--- 776,934 ----
  
  	return false;
  }
+ 
+ 
+ /*
+  * Aggref is a special case because expressions used only for ordering
+  * shouldn't be taken to conflict with each other or with regular args.  So we
+  * apply assign_expr_collations() to them rather than passing down our
+  * loccontext.
+  *
+  * Note that we recurse to each TargetEntry, not directly to its contained
+  * expression, so that the case above for T_TargetEntry will apply appropriate
+  * checks to agg ORDER BY items.
+  *
+  * We need not recurse into the aggorder or aggdistinct lists, because those
+  * contain only SortGroupClause nodes which we need not process.
+  *
+  * For ordered set functions, it's unfortunately unclear how best to proceed.
+  * The spec-defined inverse distribution functions have only one sort column
+  * and don't allow collatable types, but this is clearly unsatisfactory in the
+  * general case. Compromise by taking the sort column as part of the collation
+  * determination if, and only if, there is only one such column, and force the
+  * final choice of input collation down into the sort column if need be; but
+  * don't error out unless actually necessary (leaving it up to the function to
+  * handle the issue at runtime). This ugly wart is justified by the fact that
+  * there seems to be no other good way to get a result collation for
+  * percentile_* applied to a collatable type.
+  *
+  * But hypothetical set functions are special; they must have
+  * pairwise-assigned collations for each matching pair of args, and again we
+  * need to force the final choice of collation down into the sort column to
+  * ensure that the sort happens on the chosen collation. If there are any
+  * additional args (not allowed in the spec, but a user-defined function might
+  * have some), those contribute to the result collation in the normal way.
+  * (The hypothetical paired args never contribute to the result collation at
+  * all.)
+  */
+ 
+ static Expr *
+ relabel_expr_collation(Expr *expr, Oid newcollation)
+ {
+ 	RelabelType *node = makeNode(RelabelType);
+ 	node->arg = expr;
+ 	node->resulttype = exprType((Node *)expr);
+ 	node->resulttypmod = exprTypmod((Node *)expr);
+ 	node->resultcollid = newcollation;
+ 	node->relabelformat = COERCE_IMPLICIT_CAST;
+ 	node->location = exprLocation((Node *)expr);
+ 	return (Expr *) node;
+ }
+ 
+ static void
+ assign_aggregate_collations(Aggref *aggref,
+ 							assign_collations_context *context,
+ 							assign_collations_context *loccontext)
+ {
+ 	ListCell   *lc;
+ 
+ 	if (aggref->ishypothetical)
+ 	{
+ 		/*-
+ 		 * Hypothetical set function, i.e.
+ 		 *   func(..., a,b,c,...) within group (p,q,r,...)
+ 		 *
+ 		 * Any initial set of direct args (before "a") contributes to the
+ 		 * result collation in the usual way for function args. But none of
+ 		 * a,b,c... or p,q,r... contribute at all; instead, they must be
+ 		 * paired up (as though UNIONed) and the sorted col's collation forced
+ 		 * to the chosen value (so that we sort it correctly).
+ 		 */
+ 		int initial_args = list_length(aggref->orddirectargs) - list_length(aggref->args);
+ 		ListCell *h_arg = list_head(aggref->orddirectargs);
+ 		ListCell *s_arg = list_head(aggref->args);
+ 
+ 		Assert(initial_args >= 0);
+ 
+ 		while (initial_args-- > 0)
+ 		{
+ 			(void) assign_collations_walker((Node *) lfirst(h_arg), loccontext);
+ 			h_arg = lnext(h_arg);
+ 		}
+ 
+ 		for_each_cell(h_arg,h_arg)
+ 		{
+ 			TargetEntry *tle = (TargetEntry *) lfirst(s_arg);
+ 			Oid coll = select_common_collation(context->pstate,
+ 											   list_make2(lfirst(h_arg),lfirst(s_arg)),
+ 											   false);
+ 
+ 			/*
+ 			 * we can only get InvalidOid here if the type is not collatable,
+ 			 * so no need to try and relabel in that case.
+ 			 */
+ 
+ 			if (OidIsValid(coll)
+ 				&& coll != exprCollation((Node *)(tle->expr)))
+ 			{
+ 				tle->expr = relabel_expr_collation(tle->expr, coll);
+ 			}
+ 
+ 			s_arg = lnext(s_arg);
+ 		}
+ 	}
+ 	else if (aggref->isordset && list_length(aggref->args) == 1)
+ 	{
+ 		/*
+ 		 * Ordered set func with one sorted arg
+ 		 */
+ 		TargetEntry *tle = (TargetEntry *) linitial(aggref->args);
+ 
+ 		/* do the TLE first so that it won't error out on conflicts */
+ 
+ 		(void) assign_collations_walker((Node *) tle,
+ 										loccontext);
+ 
+ 		(void) assign_collations_walker((Node *) aggref->orddirectargs,
+ 										loccontext);
+ 
+ 		/*
+ 		 * If the sort col is a collatable type, and we chose a collation,
+ 		 * and it's not the one the sort col already has, then force the
+ 		 * sort col's collation (which can't have been explicit) to the
+ 		 * chosen one. Otherwise leave it alone.
+ 		 */
+ 		if (type_is_collatable(exprType((Node *)(tle->expr)))
+ 			&& (loccontext->strength == COLLATE_IMPLICIT
+ 				|| loccontext->strength == COLLATE_EXPLICIT)
+ 			&& exprCollation((Node *)(tle->expr)) != loccontext->collation)
+ 		{
+ 			tle->expr = relabel_expr_collation(tle->expr, loccontext->collation);
+ 		}
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * For this case, we do the direct args (if any) together, as is
+ 		 * normal for functions, but args which are either used only for
+ 		 * sorting or are only part of a WITHIN GROUP are processed
+ 		 * individually.
+ 		 */
+ 
+ 		(void) assign_collations_walker((Node *) aggref->orddirectargs,
+ 										loccontext);
+ 
+ 		foreach(lc, aggref->args)
+ 		{
+ 			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ 
+ 			Assert(IsA(tle, TargetEntry));
+ 			if (tle->resjunk)
+ 				assign_expr_collations(context->pstate,
+ 									   (Node *) tle);
+ 			else
+ 				(void) assign_collations_walker((Node *) tle,
+ 												loccontext);
+ 		}
+ 	}
+ }
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
***************
*** 463,470 **** transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
  			newresult = ParseFuncOrColumn(pstate,
  										  list_make1(n),
  										  list_make1(result),
! 										  NIL, NULL, false, false, false,
! 										  NULL, true, location);
  			if (newresult == NULL)
  				unknown_attribute(pstate, result, strVal(n), location);
  			result = newresult;
--- 463,470 ----
  			newresult = ParseFuncOrColumn(pstate,
  										  list_make1(n),
  										  list_make1(result),
! 										  location,
! 										  NULL);
  			if (newresult == NULL)
  				unknown_attribute(pstate, result, strVal(n), location);
  			result = newresult;
***************
*** 631,638 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
  					node = ParseFuncOrColumn(pstate,
  											 list_make1(makeString(colname)),
  											 list_make1(node),
! 											 NIL, NULL, false, false, false,
! 											 NULL, true, cref->location);
  				}
  				break;
  			}
--- 631,637 ----
  					node = ParseFuncOrColumn(pstate,
  											 list_make1(makeString(colname)),
  											 list_make1(node),
! 											 cref->location, NULL);
  				}
  				break;
  			}
***************
*** 676,683 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
  					node = ParseFuncOrColumn(pstate,
  											 list_make1(makeString(colname)),
  											 list_make1(node),
! 											 NIL, NULL, false, false, false,
! 											 NULL, true, cref->location);
  				}
  				break;
  			}
--- 675,681 ----
  					node = ParseFuncOrColumn(pstate,
  											 list_make1(makeString(colname)),
  											 list_make1(node),
! 											 cref->location, NULL);
  				}
  				break;
  			}
***************
*** 734,741 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
  					node = ParseFuncOrColumn(pstate,
  											 list_make1(makeString(colname)),
  											 list_make1(node),
! 											 NIL, NULL, false, false, false,
! 											 NULL, true, cref->location);
  				}
  				break;
  			}
--- 732,738 ----
  					node = ParseFuncOrColumn(pstate,
  											 list_make1(makeString(colname)),
  											 list_make1(node),
! 											 cref->location, NULL);
  				}
  				break;
  			}
***************
*** 1242,1279 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
  {
  	List	   *targs;
  	ListCell   *args;
- 	Expr	   *tagg_filter;
  
  	/* Transform the list of arguments ... */
  	targs = NIL;
  	foreach(args, fn->args)
  	{
! 		targs = lappend(targs, transformExprRecurse(pstate,
! 													(Node *) lfirst(args)));
  	}
  
- 	/*
- 	 * Transform the aggregate filter using transformWhereClause(), to which
- 	 * FILTER is virtually identical...
- 	 */
- 	tagg_filter = NULL;
- 	if (fn->agg_filter != NULL)
- 		tagg_filter = (Expr *)
- 			transformWhereClause(pstate, (Node *) fn->agg_filter,
- 								 EXPR_KIND_FILTER, "FILTER");
- 
  	/* ... and hand off to ParseFuncOrColumn */
  	return ParseFuncOrColumn(pstate,
  							 fn->funcname,
  							 targs,
! 							 fn->agg_order,
! 							 tagg_filter,
! 							 fn->agg_star,
! 							 fn->agg_distinct,
! 							 fn->func_variadic,
! 							 fn->over,
! 							 false,
! 							 fn->location);
  }
  
  static Node *
--- 1239,1258 ----
  {
  	List	   *targs;
  	ListCell   *args;
  
  	/* Transform the list of arguments ... */
  	targs = NIL;
  	foreach(args, fn->args)
  	{
! 		targs = lappend(targs, transformExprRecurse(pstate, (Node *) lfirst(args)));
  	}
  
  	/* ... and hand off to ParseFuncOrColumn */
  	return ParseFuncOrColumn(pstate,
  							 fn->funcname,
  							 targs,
! 							 fn->location,
! 							 fn);
  }
  
  static Node *
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
***************
*** 17,32 ****
--- 17,35 ----
  #include "access/htup_details.h"
  #include "catalog/pg_proc.h"
  #include "catalog/pg_type.h"
+ #include "catalog/pg_aggregate.h"
  #include "funcapi.h"
  #include "lib/stringinfo.h"
  #include "nodes/makefuncs.h"
  #include "nodes/nodeFuncs.h"
  #include "parser/parse_agg.h"
+ #include "parser/parse_clause.h"
  #include "parser/parse_coerce.h"
  #include "parser/parse_func.h"
  #include "parser/parse_relation.h"
  #include "parser/parse_target.h"
  #include "parser/parse_type.h"
+ #include "parser/parse_expr.h"
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
  #include "utils/syscache.h"
***************
*** 56,70 **** static Node *ParseComplexProjection(ParseState *pstate, char *funcname,
   *	Also, when is_column is true, we return NULL on failure rather than
   *	reporting a no-such-function error.
   *
!  *	The argument expressions (in fargs) and filter must have been transformed
!  *	already.  But the agg_order expressions, if any, have not been.
   */
  Node *
  ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
! 				  List *agg_order, Expr *agg_filter,
! 				  bool agg_star, bool agg_distinct, bool func_variadic,
! 				  WindowDef *over, bool is_column, int location)
  {
  	Oid			rettype;
  	Oid			funcid;
  	ListCell   *l;
--- 59,79 ----
   *	Also, when is_column is true, we return NULL on failure rather than
   *	reporting a no-such-function error.
   *
!  *	The argument expressions (in fargs) must have been transformed
!  *	already.
   */
  Node *
  ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
! 				  int location, FuncCall *fn)
  {
+ 	List       *agg_order = (fn ? fn->agg_order : NIL);
+ 	Expr       *agg_filter = NULL;
+ 	bool        agg_star = (fn ? fn->agg_star : false);
+ 	bool        agg_distinct = (fn ? fn->agg_distinct : false);
+ 	bool        agg_within_group = (fn ? fn->has_within_group : false);
+ 	bool        func_variadic = (fn ? fn->func_variadic : false);
+ 	WindowDef  *over = (fn ? fn->over : NULL);
+ 	bool        is_column = (fn == NULL);
  	Oid			rettype;
  	Oid			funcid;
  	ListCell   *l;
***************
*** 81,86 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 90,101 ----
  	int			nvargs;
  	Oid			vatype;
  	FuncDetailCode fdresult;
+ 	int		number_of_args = -1;
+ 	bool		isordsetfunc = false;
+ 	bool		ishypotheticalsetfunc = false;
+ 
+ 	/* Check if the function has WITHIN GROUP as well as distinct. */
+ 	Assert(!(agg_within_group && agg_distinct));
  
  	/*
  	 * Most of the rest of the parser just assumes that functions do not have
***************
*** 98,103 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 113,127 ----
  				 parser_errposition(pstate, location)));
  
  	/*
+ 	 * Transform the aggregate filter using transformWhereClause(), to which
+ 	 * FILTER is virtually identical...
+ 	 */
+ 	if (fn && fn->agg_filter != NULL)
+ 		agg_filter = (Expr *)
+ 			transformWhereClause(pstate, (Node *) fn->agg_filter,
+ 								 EXPR_KIND_FILTER, "FILTER");
+ 
+ 	/*
  	 * Extract arg type info in preparation for function lookup.
  	 *
  	 * If any arguments are Param markers of type VOID, we discard them from
***************
*** 163,168 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 187,198 ----
  		}
  	}
  
+ 	if (agg_within_group && argnames)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("ordered set functions cannot use named arguments"),
+ 				 parser_errposition(pstate, location)));
+ 
  	if (fargs)
  	{
  		first_arg = linitial(fargs);
***************
*** 170,175 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 200,225 ----
  	}
  
  	/*
+ 	 * If WITHIN GROUP is present, we need to call transformExpr on each
+ 	 * SortBy node in agg_order, then call exprType and append to
+ 	 * actual_arg_types, in order to get the types of values in WITHIN GROUP
+ 	 * clause.
+ 	 */
+ 	if (agg_within_group)
+ 	{
+ 		Assert(agg_order != NIL);
+ 
+ 		foreach(l, agg_order)
+ 		{
+ 			SortBy	   *arg = (SortBy *) lfirst(l);
+ 
+ 			arg->node = transformExpr(pstate, arg->node, EXPR_KIND_ORDER_BY);
+ 
+ 			actual_arg_types[nargs++] = exprType(arg->node);
+ 		}
+ 	}
+ 
+ 	/*
  	 * Check for column projection: if function has one argument, and that
  	 * argument is of complex type, and function name is not qualified, then
  	 * the "function call" could be a projection.  We also check that there
***************
*** 247,252 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 297,308 ----
  			errmsg("DISTINCT specified, but %s is not an aggregate function",
  				   NameListToString(funcname)),
  					 parser_errposition(pstate, location)));
+ 		if (agg_within_group)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 			errmsg("WITHIN GROUP specified, but %s is not an ordered set function",
+ 				   NameListToString(funcname)),
+ 					 parser_errposition(pstate, location)));
  		if (agg_order != NIL)
  			ereport(ERROR,
  					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 266,271 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 322,374 ----
  							NameListToString(funcname)),
  					 parser_errposition(pstate, location)));
  	}
+ 	else if (fdresult == FUNCDETAIL_AGGREGATE)
+ 	{
+ 		HeapTuple	tup;
+ 		Form_pg_aggregate classForm;
+ 
+ 		tup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(funcid));
+ 		if (!HeapTupleIsValid(tup)) /* should not happen */
+ 			elog(ERROR, "cache lookup failed for aggregate %u", funcid);
+ 
+ 		classForm = (Form_pg_aggregate) GETSTRUCT(tup);
+ 		isordsetfunc = classForm->aggisordsetfunc;
+ 
+ 		if (isordsetfunc)
+ 		{
+ 			if (classForm->aggordnargs == -2)
+ 			{
+ 				ishypotheticalsetfunc = true;
+ 
+ 				if (nvargs != 2*list_length(agg_order))
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 							 errmsg("function %s has %d ordering columns but %d hypothetical arguments",
+ 								NameListToString(funcname), list_length(agg_order), (nvargs - list_length(agg_order))),
+ 							 parser_errposition(pstate, location)));
+ 			}
+ 			else
+ 			{
+ 				number_of_args = classForm->aggordnargs;
+ 			}
+ 
+ 			if (!agg_within_group)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 						 errmsg("WITHIN GROUP is required for call to ordered set function %s",
+ 								NameListToString(funcname)),
+ 						 parser_errposition(pstate, location)));
+ 		
+ 			if (over)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("OVER clause not supported for call to ordered set function %s",
+ 								NameListToString(funcname)),
+ 						 parser_errposition(pstate, location)));
+ 		}
+ 
+ 		ReleaseSysCache(tup);
+ 	}
  	else if (!(fdresult == FUNCDETAIL_AGGREGATE ||
  			   fdresult == FUNCDETAIL_WINDOWFUNC))
  	{
***************
*** 351,363 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
  											   false);
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, fargs, actual_arg_types, declared_arg_types);
  
  	/*
  	 * If it's a variadic function call, transform the last nvargs arguments
  	 * into an array --- unless it's an "any" variadic.
  	 */
! 	if (nvargs > 0 && declared_arg_types[nargs - 1] != ANYOID)
  	{
  		ArrayExpr  *newa = makeNode(ArrayExpr);
  		int			non_var_args = nargs - nvargs;
--- 454,469 ----
  											   false);
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, fargs, (isordsetfunc) ? agg_order : NIL, 
! 					  actual_arg_types, 
! 					  declared_arg_types,
! 					  ishypotheticalsetfunc);
  
  	/*
  	 * If it's a variadic function call, transform the last nvargs arguments
  	 * into an array --- unless it's an "any" variadic.
  	 */
! 	if (nvargs > 0 && vatype != ANYOID)
  	{
  		ArrayExpr  *newa = makeNode(ArrayExpr);
  		int			non_var_args = nargs - nvargs;
***************
*** 388,403 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
  	 * When function is called with an explicit VARIADIC labeled parameter,
  	 * and the declared_arg_type is "any", then sanity check the actual
  	 * parameter type now - it must be an array.
  	 */
  	if (nargs > 0 && vatype == ANYOID && func_variadic)
  	{
! 		Oid		va_arr_typid = actual_arg_types[nargs - 1];
  
  		if (!OidIsValid(get_element_type(va_arr_typid)))
  			ereport(ERROR,
  					(errcode(ERRCODE_DATATYPE_MISMATCH),
  					 errmsg("VARIADIC argument must be an array"),
! 			  parser_errposition(pstate, exprLocation((Node *) llast(fargs)))));
  	}
  
  	/* build the appropriate output structure */
--- 494,524 ----
  	 * When function is called with an explicit VARIADIC labeled parameter,
  	 * and the declared_arg_type is "any", then sanity check the actual
  	 * parameter type now - it must be an array.
+ 	 *
+ 	 * Also, it can't be a hypothetical set function, and if it's an ordered
+ 	 * set function, the variadic labeled parameter is the last _direct_ arg,
+ 	 * not an ordered arg. (In practice we're unlikely to get this far for
+ 	 * hypotheticals, since make_fn_arguments would probably fail to unify
+ 	 * types, but we can't change the order of these.)
  	 */
  	if (nargs > 0 && vatype == ANYOID && func_variadic)
  	{
! 		int     ignore_args = (agg_within_group ? list_length(agg_order) : 0);
! 		Oid		va_arr_typid = actual_arg_types[nargs - 1 - ignore_args];
! 
! 		if (ishypotheticalsetfunc)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_SYNTAX_ERROR),
! 			  errmsg("explicit VARIADIC argument not allowed for hypothetical set function"),
! 			  parser_errposition(pstate,
! 								 exprLocation((Node *) list_nth(fargs, nargs - 1 - ignore_args)))));
  
  		if (!OidIsValid(get_element_type(va_arr_typid)))
  			ereport(ERROR,
  					(errcode(ERRCODE_DATATYPE_MISMATCH),
  					 errmsg("VARIADIC argument must be an array"),
! 			  parser_errposition(pstate,
! 								 exprLocation((Node *) list_nth(fargs, nargs - 1 - ignore_args)))));
  	}
  
  	/* build the appropriate output structure */
***************
*** 421,426 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 542,553 ----
  		/* aggregate function */
  		Aggref	   *aggref = makeNode(Aggref);
  
+ 		if (agg_within_group && !isordsetfunc)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 					 errmsg("%s is not an ordered set function",
+ 							func_signature_string(funcname, nargs, NIL, actual_arg_types))));
+ 
  		aggref->aggfnoid = funcid;
  		aggref->aggtype = rettype;
  		/* aggcollid and inputcollid will be set by parse_collate.c */
***************
*** 428,441 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
  		aggref->aggfilter = agg_filter;
  		aggref->aggstar = agg_star;
  		aggref->aggvariadic = func_variadic;
  		/* agglevelsup will be set by transformAggregateCall */
  		aggref->location = location;
  
  		/*
  		 * Reject attempt to call a parameterless aggregate without (*)
  		 * syntax.	This is mere pedantry but some folks insisted ...
  		 */
! 		if (fargs == NIL && !agg_star)
  			ereport(ERROR,
  					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
  					 errmsg("%s(*) must be used to call a parameterless aggregate function",
--- 555,578 ----
  		aggref->aggfilter = agg_filter;
  		aggref->aggstar = agg_star;
  		aggref->aggvariadic = func_variadic;
+ 		aggref->ishypothetical = ishypotheticalsetfunc;
  		/* agglevelsup will be set by transformAggregateCall */
  		aggref->location = location;
  
+ 		if (isordsetfunc
+ 			&& number_of_args >= 0
+ 			&& number_of_args != list_length(fargs))
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 					 errmsg("incorrect number of direct arguments to ordered set function %s",
+ 							NameListToString(funcname)),
+ 					 parser_errposition(pstate, location)));
+ 
  		/*
  		 * Reject attempt to call a parameterless aggregate without (*)
  		 * syntax.	This is mere pedantry but some folks insisted ...
  		 */
! 		if (fargs == NIL && !agg_star && !agg_within_group)
  			ereport(ERROR,
  					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
  					 errmsg("%s(*) must be used to call a parameterless aggregate function",
***************
*** 464,470 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
  					 parser_errposition(pstate, location)));
  
  		/* parse_agg.c does additional aggregate-specific processing */
! 		transformAggregateCall(pstate, aggref, fargs, agg_order, agg_distinct);
  
  		retval = (Node *) aggref;
  	}
--- 601,608 ----
  					 parser_errposition(pstate, location)));
  
  		/* parse_agg.c does additional aggregate-specific processing */
! 		transformAggregateCall(pstate, aggref, fargs, agg_order,
! 							   agg_distinct, agg_within_group);
  
  		retval = (Node *) aggref;
  	}
***************
*** 473,478 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 611,622 ----
  		/* window function */
  		WindowFunc *wfunc = makeNode(WindowFunc);
  
+ 		if (agg_within_group)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 					 errmsg("WITHIN GROUP not allowed in window functions"),
+ 					 parser_errposition(pstate, location)));
+ 
  		/*
  		 * True window functions must be called with a window definition.
  		 */
***************
*** 1363,1373 **** func_get_detail(List *funcname,
  void
  make_fn_arguments(ParseState *pstate,
  				  List *fargs,
  				  Oid *actual_arg_types,
! 				  Oid *declared_arg_types)
  {
  	ListCell   *current_fargs;
  	int			i = 0;
  
  	foreach(current_fargs, fargs)
  	{
--- 1507,1527 ----
  void
  make_fn_arguments(ParseState *pstate,
  				  List *fargs,
+ 				  List *agg_order,
  				  Oid *actual_arg_types,
! 				  Oid *declared_arg_types,
! 				  bool requiresUnification)
  {
  	ListCell   *current_fargs;
+ 	ListCell   *current_aoargs;
  	int			i = 0;
+ 	int         unify_offset = -1;
+ 
+ 	if (requiresUnification)
+ 	{
+ 		unify_offset = list_length(fargs) - list_length(agg_order);
+ 		Assert(unify_offset >= 0);
+ 	}
  
  	foreach(current_fargs, fargs)
  	{
***************
*** 1375,1380 **** make_fn_arguments(ParseState *pstate,
--- 1529,1535 ----
  		if (actual_arg_types[i] != declared_arg_types[i])
  		{
  			Node	   *node = (Node *) lfirst(current_fargs);
+ 			Node       *temp = NULL;
  
  			/*
  			 * If arg is a NamedArgExpr, coerce its input expr instead --- we
***************
*** 1395,1412 **** make_fn_arguments(ParseState *pstate,
  			}
  			else
  			{
! 				node = coerce_type(pstate,
! 								   node,
! 								   actual_arg_types[i],
! 								   declared_arg_types[i], -1,
! 								   COERCION_IMPLICIT,
! 								   COERCE_IMPLICIT_CAST,
! 								   -1);
! 				lfirst(current_fargs) = node;
  			}
  		}
  		i++;
  	}
  }
  
  /*
--- 1550,1615 ----
  			}
  			else
  			{
! 				/* 
! 				 * If we are dealing with a hypothetical set function, we
! 				 * need to unify agg_order and fargs.
! 				 */
! 
! 				if (declared_arg_types[i] == ANYOID && requiresUnification)
! 				{
! 					Oid unification_oid;
! 					SortBy *unify_with = (SortBy *) list_nth(agg_order,i - unify_offset);
! 
! 					unification_oid = select_common_type(pstate,
! 														 list_make2(unify_with->node,node),
! 														 "WITHIN GROUP",
! 														 NULL);
! 
! 					declared_arg_types[i + list_length(agg_order)] = unification_oid;
! 
! 					temp = coerce_type(pstate,
! 									   node,
! 									   actual_arg_types[i],
! 									   unification_oid, -1,
! 									   COERCION_IMPLICIT,
! 									   COERCE_IMPLICIT_CAST,
! 									   -1);
! 				}
! 				else
! 				{
! 					temp = coerce_type(pstate,
! 									   node,
! 									   actual_arg_types[i],
! 									   declared_arg_types[i], -1,
! 									   COERCION_IMPLICIT,
! 									   COERCE_IMPLICIT_CAST,
! 									   -1);
! 				}
! 
! 				lfirst(current_fargs) = temp;
  			}
  		}
  		i++;
  	}
+ 
+ 	foreach(current_aoargs, agg_order)
+ 	{
+ 		if (actual_arg_types[i] != declared_arg_types[i])
+ 		{
+ 			SortBy	   *node = (SortBy *) lfirst(current_aoargs);
+ 			Node       *temp = NULL;
+ 
+ 			temp = coerce_type(pstate,
+ 							   node->node,
+ 							   actual_arg_types[i],
+ 							   declared_arg_types[i], -1,
+ 							   COERCION_IMPLICIT,
+ 							   COERCE_IMPLICIT_CAST,
+ 							   -1);
+ 			node->node = temp;
+ 		}
+ 		i++;
+ 	}
  }
  
  /*
*** a/src/backend/parser/parse_oper.c
--- b/src/backend/parser/parse_oper.c
***************
*** 823,829 **** make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
  											   false);
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
  
  	/* and build the expression node */
  	result = makeNode(OpExpr);
--- 823,829 ----
  											   false);
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types, false);
  
  	/* and build the expression node */
  	result = makeNode(OpExpr);
***************
*** 953,959 **** make_scalar_array_op(ParseState *pstate, List *opname,
  	declared_arg_types[1] = res_atypeId;
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
  
  	/* and build the expression node */
  	result = makeNode(ScalarArrayOpExpr);
--- 953,959 ----
  	declared_arg_types[1] = res_atypeId;
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types, false);
  
  	/* and build the expression node */
  	result = makeNode(ScalarArrayOpExpr);
*** a/src/backend/utils/adt/Makefile
--- b/src/backend/utils/adt/Makefile
***************
*** 19,31 **** OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
  	array_userfuncs.o arrayutils.o bool.o \
  	cash.o char.o date.o datetime.o datum.o domains.o \
  	enum.o float.o format_type.o \
! 	geo_ops.o geo_selfuncs.o int.o int8.o json.o jsonfuncs.o like.o \
  	lockfuncs.o misc.o nabstime.o name.o numeric.o numutils.o \
  	oid.o oracle_compat.o pseudotypes.o rangetypes.o rangetypes_gist.o \
  	rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \
  	tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \
  	network.o mac.o inet_cidr_ntop.o inet_net_pton.o \
! 	ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \
  	ascii.o quote.o pgstatfuncs.o encode.o dbsize.o genfile.o trigfuncs.o \
  	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
  	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
--- 19,31 ----
  	array_userfuncs.o arrayutils.o bool.o \
  	cash.o char.o date.o datetime.o datum.o domains.o \
  	enum.o float.o format_type.o \
! 	geo_ops.o geo_selfuncs.o hypotheticalset.o int.o int8.o json.o jsonfuncs.o like.o \
  	lockfuncs.o misc.o nabstime.o name.o numeric.o numutils.o \
  	oid.o oracle_compat.o pseudotypes.o rangetypes.o rangetypes_gist.o \
  	rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \
  	tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \
  	network.o mac.o inet_cidr_ntop.o inet_net_pton.o \
! 	inversedistribution.o ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \
  	ascii.o quote.o pgstatfuncs.o encode.o dbsize.o genfile.o trigfuncs.o \
  	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
  	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
*** /dev/null
--- b/src/backend/utils/adt/hypotheticalset.c
***************
*** 0 ****
--- 1,223 ----
+ /*-------------------------------------------------------------------------
+  *
+  * hypotheticalset.c
+  *	  Hypothetical set functions.
+  *
+  * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  *
+  * IDENTIFICATION
+  *	  src/backend/utils/adt/hypotheticalset.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ #include "postgres.h"
+ #include "fmgr.h"
+ #include <string.h>
+ #include <math.h>
+ 
+ #include "utils/tuplesort.h"
+ #include "catalog/pg_type.h"
+ #include "utils/datetime.h"
+ #include "utils/builtins.h"
+ #include "executor/executor.h"
+ 
+ Datum hypothetical_rank_final(PG_FUNCTION_ARGS);
+ Datum hypothetical_dense_rank_final(PG_FUNCTION_ARGS);
+ Datum hypothetical_percent_rank_final(PG_FUNCTION_ARGS);
+ Datum hypothetical_cume_dist_final(PG_FUNCTION_ARGS);
+ 
+ 
+ /*
+  * Common code to sanity-check args for hypothetical set functions. No need
+  * for friendly errors, these can only happen if someone's messing up the
+  * aggregate definitions. The checks are needed for security, however; but we
+  * only need them once per call site.  Store a pointer to the tupdesc as a
+  * sentinel.
+  */
+ 
+ static void
+ hypothetical_check_argtypes(FunctionCallInfo fcinfo, int nargs, TupleDesc tupdesc)
+ {
+ 	int i;
+ 
+ 	if (!tupdesc
+ 		|| (nargs + 1) != tupdesc->natts
+ 		|| tupdesc->attrs[nargs]->atttypid != BOOLOID)
+ 		elog(ERROR, "type mismatch in hypothetical set function");
+ 
+ 	for (i = 0; i < nargs; ++i)
+ 		if (get_fn_expr_argtype(fcinfo->flinfo,i) != tupdesc->attrs[i]->atttypid)
+ 			elog(ERROR, "type mismatch in hypothetical set function");
+ 
+ 	fcinfo->flinfo->fn_extra = tupdesc;
+ }
+ 
+ /*
+  * rank(float8)  - rank of hypothetical row
+  */
+ Datum
+ hypothetical_rank_final(PG_FUNCTION_ARGS)
+ {
+ 	Tuplesortstate *sorter = NULL;
+ 	TupleDesc	tupdesc = NULL;
+     TupleTableSlot *slot = NULL;
+ 	Oid			datumtype = InvalidOid;
+ 	int			nargs = PG_NARGS();
+ 	int			i;
+ 	int64		rank = 1;
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, &tupdesc, &slot, &datumtype);
+ 
+ 	if (fcinfo->flinfo->fn_extra == NULL
+ 		|| fcinfo->flinfo->fn_extra != tupdesc)
+ 		hypothetical_check_argtypes(fcinfo, nargs, tupdesc);
+ 
+ 	/* insert the hypothetical row into the sort */
+ 
+ 	ExecClearTuple(slot);
+ 	for (i = 0; i < nargs; ++i)
+ 	{
+ 		slot->tts_values[i] = PG_GETARG_DATUM(i);
+ 		slot->tts_isnull[i] = PG_ARGISNULL(i);
+ 	}
+ 	slot->tts_values[nargs] = BoolGetDatum(true);
+ 	slot->tts_isnull[nargs] = false;
+ 	ExecStoreVirtualTuple(slot);
+ 
+ 	tuplesort_puttupleslot(sorter, slot);
+ 
+ 	tuplesort_performsort(sorter);
+ 
+ 	while (tuplesort_gettupleslot(sorter, true, slot))
+ 	{
+ 		bool isnull;
+ 		Datum d = slot_getattr(slot, nargs + 1, &isnull);
+ 
+ 		if (!isnull && DatumGetBool(d))
+ 			break;
+ 
+ 		++rank;
+ 	}
+ 
+ 	ExecClearTuple(slot);
+ 
+ 	PG_RETURN_INT64(rank);
+ }
+ 
+ /*
+  * dense_rank(float8)  - rank of hypothetical row
+  *                       without gap in ranking
+  */
+ Datum
+ hypothetical_dense_rank_final(PG_FUNCTION_ARGS)
+ {
+ 	Tuplesortstate *sorter = NULL;
+ 	TupleDesc	tupdesc = NULL;
+     TupleTableSlot *slot = NULL;
+ 	Oid			datumtype = InvalidOid;
+ 	int			nargs = PG_NARGS();
+ 	int			i;
+ 	int64		rank = 1;
+ 	int			duplicate_count = 0;
+ 	TupleTableSlot *slot2 = NULL;
+ 	AttrNumber *colidx;
+ 	FmgrInfo   *equalfns;
+ 	int			numDistinctCol = 0;
+ 	MemoryContext memcontext;
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, &tupdesc, &slot, &datumtype);
+ 
+ 	if (fcinfo->flinfo->fn_extra == NULL
+ 		|| fcinfo->flinfo->fn_extra != tupdesc)
+ 		hypothetical_check_argtypes(fcinfo, nargs, tupdesc);
+ 
+ 	/* insert the hypothetical row into the sort */
+ 
+ 	ExecClearTuple(slot);
+ 	for (i = 0; i < nargs; ++i)
+ 	{
+ 		slot->tts_values[i] = PG_GETARG_DATUM(i);
+ 		slot->tts_isnull[i] = PG_ARGISNULL(i);
+ 	}
+ 	slot->tts_values[nargs] = BoolGetDatum(true);
+ 	slot->tts_isnull[nargs] = false;
+ 	ExecStoreVirtualTuple(slot);
+ 
+ 	tuplesort_puttupleslot(sorter, slot);
+ 
+ 	tuplesort_performsort(sorter);
+ 
+ 	numDistinctCol = AggSetGetDistinctInfo(fcinfo, &slot2, &colidx, &equalfns);
+ 
+ 	ExecClearTuple(slot2);
+ 
+ 	AggSetGetPerTupleContext(fcinfo, &memcontext);
+ 
+ 	while (tuplesort_gettupleslot(sorter, true, slot))
+ 	{
+ 		TupleTableSlot *tmpslot = slot2;
+ 		bool isnull;
+ 		Datum d = slot_getattr(slot, nargs + 1, &isnull);
+ 
+ 		if (!isnull && DatumGetBool(d))
+ 			break;
+ 
+ 		if (!TupIsNull(slot2)
+ 			&& execTuplesMatch(slot, slot2,
+ 							   (numDistinctCol - 1),
+ 							   colidx,
+ 							   equalfns,
+ 							   memcontext))
+ 			++duplicate_count;
+ 
+ 		slot2 = slot;
+ 		slot = tmpslot;
+ 
+ 		++rank;
+ 	}
+ 
+ 	ExecClearTuple(slot);
+ 	ExecClearTuple(slot2);
+ 
+ 	rank = rank - duplicate_count;
+ 	PG_RETURN_INT64(rank);
+ }
+ 
+ /* percent_rank(float8)
+  * Calculates the relative ranking of hypothetical
+  * row within a group
+  */
+ 
+ Datum
+ hypothetical_percent_rank_final(PG_FUNCTION_ARGS)
+ {
+ 	Datum		rank     = hypothetical_rank_final(fcinfo);
+ 	int64		rank_val = DatumGetInt64(rank);
+ 	int64		rowcount = AggSetGetRowCount(fcinfo) + 1;
+ 	float8 		result_val = 0.0;
+ 
+ 	if (rowcount == 1)
+ 		PG_RETURN_FLOAT8(0);
+ 
+ 	result_val = (float8) (rank_val - 1) / (float8) (rowcount - 1);
+ 
+ 	PG_RETURN_FLOAT8(result_val);
+ }
+ 
+ /* cume_dist - cumulative distribution of hypothetical
+  * row in a group
+  */
+ 
+ Datum
+ hypothetical_cume_dist_final(PG_FUNCTION_ARGS)
+ {
+ 	Datum		rank     = hypothetical_rank_final(fcinfo);
+ 	int64		rank_val = DatumGetInt64(rank);
+ 	int64		rowcount = AggSetGetRowCount(fcinfo) + 1;
+ 
+ 	float8 result_val = (float8) (rank_val) / (float8) (rowcount);
+ 
+ 	PG_RETURN_FLOAT8(result_val);
+ }
*** /dev/null
--- b/src/backend/utils/adt/inversedistribution.c
***************
*** 0 ****
--- 1,662 ----
+ /*-------------------------------------------------------------------------
+  *
+  * inversedistribution.c
+  *	  Inverse distribution functions.
+  *
+  * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  *
+  * IDENTIFICATION
+  *	  src/backend/utils/adt/inversedistribution.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ #include "postgres.h"
+ #include "fmgr.h"
+ #include <string.h>
+ #include <math.h>
+ 
+ #include "utils/tuplesort.h"
+ #include "catalog/pg_type.h"
+ #include "utils/datetime.h"
+ #include "utils/lsyscache.h"
+ #include "utils/array.h"
+ 
+ /*
+  * percentile_disc(float8)  - discrete percentile
+  */
+ 
+ Datum percentile_disc_final(PG_FUNCTION_ARGS);
+ 
+ Datum
+ percentile_disc_final(PG_FUNCTION_ARGS)
+ {
+ 	float8		percentile;
+ 	Tuplesortstate *sorter;
+ 	Oid			datumtype;
+ 	Datum		val;
+ 	bool		isnull;
+ 	int64		skiprows;
+ 	int64		rowcount   = AggSetGetRowCount(fcinfo);
+ 
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	percentile = PG_GETARG_FLOAT8(0);
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ 
+ 	if (percentile < 0 || percentile > 1 || isnan(percentile))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("percentile value %g must be between 0 and 1", percentile)));
+ 
+ 	if (rowcount < 1)
+ 		PG_RETURN_NULL();
+ 
+ 	tuplesort_performsort(sorter);
+ 
+ 	/*
+ 	 * We need the smallest K such that (K/N) >= percentile. K starts at 1.
+ 	 * Therefore K >= N*percentile
+ 	 * Therefore K = ceil(N*percentile)
+ 	 * So we skip K-1 rows (if K>0) and return the next row fetched.
+ 	 *
+ 	 * We don't actually expect to see nulls in the input, our strict flag
+ 	 * should have filtered them out, but we're required to not crash if
+ 	 * there is one.
+ 	 */
+ 
+ 	skiprows = (int64) ceil(percentile * rowcount);
+ 	Assert(skiprows <= rowcount);
+ 
+ 	while (--skiprows > 0)
+ 		if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ 			elog(ERROR,"missing row in percentile_disc");
+ 
+ 	if (!tuplesort_getdatum(sorter, true, &val, &isnull))
+ 		elog(ERROR,"missing row in percentile_disc");
+ 
+ 	if (isnull)
+ 		PG_RETURN_NULL();
+ 	else
+ 		PG_RETURN_DATUM(val);
+ }
+ 
+ 
+ /*
+  * For percentile_cont, we need a way to interpolate between consecutive
+  * values. Use a helper function for that, so that we can share the rest
+  * of the code between types.
+  */
+ 
+ static Datum float8_lerp(Datum lo, Datum hi, float8 pct)
+ {
+ 	float8 loval = DatumGetFloat8(lo);
+ 	float8 hival = DatumGetFloat8(hi); 
+ 	return Float8GetDatum(loval + (pct * (hival - loval)));
+ }
+ 
+ static Datum interval_lerp(Datum lo, Datum hi, float8 pct)
+ {
+ 	Datum diff_result = DirectFunctionCall2(interval_mi, hi, lo);
+ 	Datum mul_result  = DirectFunctionCall2(interval_mul,
+ 											diff_result,
+ 											Float8GetDatumFast(pct));
+ 	return DirectFunctionCall2(interval_pl, mul_result, lo);
+ }
+ 
+ typedef Datum (*LerpFunc)(Datum lo, Datum hi, float8 pct);
+ 
+ static Datum
+ percentile_cont_final_common(FunctionCallInfo fcinfo,
+ 							 Oid expect_type,
+ 							 LerpFunc lerpfunc)
+ {
+ 	float8		percentile;
+ 	int64		rowcount = AggSetGetRowCount(fcinfo);
+ 	Tuplesortstate *sorter;
+ 	Oid			datumtype;
+ 	Datum		val;
+ 	Datum		first_row;
+ 	Datum		second_row;
+ 	float8		proportion;
+ 	bool		isnull;
+ 	int64		skiprows;
+ 	int64		lower_row = 0;
+ 	int64		higher_row = 0;
+ 
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	percentile = PG_GETARG_FLOAT8(0);
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ 
+ 	Assert(datumtype == expect_type);
+ 
+ 	if (percentile < 0 || percentile > 1 || isnan(percentile))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("percentile value %g must be between 0 and 1", percentile)));
+ 
+ 	if (rowcount < 1)
+ 		PG_RETURN_NULL();
+ 
+ 	tuplesort_performsort(sorter);
+ 
+ 	lower_row = floor(percentile * (rowcount - 1));
+ 	higher_row = ceil(percentile * (rowcount - 1));
+ 
+ 	Assert(lower_row < rowcount);
+ 
+ 	for (skiprows = lower_row; skiprows > 0; --skiprows)
+ 		if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ 			elog(ERROR,"missing row in percentile_cont");
+ 
+ 	if (!tuplesort_getdatum(sorter, true, &first_row, &isnull))
+ 		elog(ERROR,"missing row in percentile_cont");
+ 	if (isnull)
+ 		PG_RETURN_NULL();
+ 
+ 	if (lower_row == higher_row)
+ 	{
+ 		val = first_row;
+ 	}
+ 	else
+ 	{
+ 		if (!tuplesort_getdatum(sorter, true, &second_row, &isnull))
+ 			elog(ERROR,"missing row in percentile_cont");
+ 
+ 		if (isnull)
+ 			PG_RETURN_NULL();
+ 
+ 		proportion = (percentile * (rowcount-1)) - lower_row;
+ 		val = lerpfunc(first_row, second_row, proportion);
+ 	}
+ 
+ 	if (isnull)
+ 		PG_RETURN_NULL();
+ 	else
+ 		PG_RETURN_DATUM(val);
+ }
+ 
+ 
+ 
+ /*
+  * percentile_cont(float8)  - continuous percentile
+  */
+ 
+ Datum percentile_cont_float8_final(PG_FUNCTION_ARGS);
+ Datum percentile_cont_interval_final(PG_FUNCTION_ARGS);
+ 
+ Datum
+ percentile_cont_float8_final(PG_FUNCTION_ARGS)
+ {
+ 	return percentile_cont_final_common(fcinfo, FLOAT8OID, float8_lerp);
+ }
+ 
+ /*
+  * percentile_interval_cont(Interval)  - continuous percentile for Interval
+  */
+ 
+ Datum
+ percentile_cont_interval_final(PG_FUNCTION_ARGS)
+ {
+ 	return percentile_cont_final_common(fcinfo, INTERVALOID, interval_lerp);
+ }
+ 
+ 
+ /*
+  * mode() - most common value
+  */
+ 
+ Datum mode_final(PG_FUNCTION_ARGS);
+ 
+ Datum
+ mode_final(PG_FUNCTION_ARGS)
+ {
+ 	Tuplesortstate *sorter;
+ 	Oid			datumtype;
+ 	bool		isnull;
+ 	Datum		val;
+ 	Datum		last_val = (Datum) 0;
+ 	bool		last_val_is_mode = false;
+ 	int64		val_freq = 0;
+ 	Datum		mode_val = (Datum) 0;
+ 	int64		mode_freq = 0;
+ 	FmgrInfo   *equalfn;
+ 	bool		shouldfree;
+ 
+ 	struct mode_type_info {
+ 		Oid typid;
+ 		int16 typLen;
+ 		bool typByVal;
+ 	} *typinfo = fcinfo->flinfo->fn_extra;
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ 	AggSetGetDistinctInfo(fcinfo, NULL, NULL, &equalfn);
+ 
+ 	if (!typinfo || typinfo->typid != datumtype)
+ 	{
+ 		if (typinfo)
+ 			pfree(typinfo);
+ 		typinfo = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 									 sizeof(struct mode_type_info));
+ 		typinfo->typid = datumtype;
+ 		get_typlenbyval(datumtype, &typinfo->typLen, &typinfo->typByVal);
+ 	}
+ 
+ 	shouldfree = !(typinfo->typByVal);
+ 
+ 	tuplesort_performsort(sorter);
+ 
+ 	while (tuplesort_getdatum(sorter, true, &val, &isnull))
+ 	{
+ 		if (isnull)
+ 			continue;
+ 
+ 		if (val_freq == 0)
+ 		{
+ 			/* first value - assume modal until shown otherwise */
+ 			mode_val = last_val = val;
+ 			mode_freq = val_freq = 1;
+ 			last_val_is_mode = true;
+ 		}
+ 		else if (DatumGetBool(FunctionCall2(equalfn, val, last_val)))
+ 		{
+ 			/* value equal to previous value */
+ 			if (last_val_is_mode)
+ 				++mode_freq;
+ 			else if (++val_freq > mode_freq)
+ 			{
+ 				if (shouldfree)
+ 				{
+ 					pfree(DatumGetPointer(mode_val));
+ 					pfree(DatumGetPointer(val));
+ 				}
+ 
+ 				mode_val = last_val;
+ 				mode_freq = val_freq;
+ 				last_val_is_mode = true;
+ 			}
+ 			else if (shouldfree)
+ 				pfree(DatumGetPointer(val));
+ 		}
+ 		else
+ 		{
+ 			if (shouldfree && !last_val_is_mode)
+ 				pfree(DatumGetPointer(last_val));
+ 
+ 			last_val_is_mode = false;
+ 			last_val = val;
+ 			val_freq = 1;
+ 		}
+ 	}
+ 
+ 	if (shouldfree && !last_val_is_mode)
+ 		pfree(DatumGetPointer(last_val));
+ 
+ 	if (mode_freq)
+ 		PG_RETURN_DATUM(mode_val);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ 
+ 
+ /*
+  * percentile_disc(float8[])  - discrete percentiles
+  */
+ 
+ Datum percentile_disc_multi_final(PG_FUNCTION_ARGS);
+ 
+ struct pct_info {
+ 	int64	first_row;
+ 	int64	second_row;
+ 	float8	proportion;
+ 	int		idx;
+ };
+ 
+ static int pct_info_cmp(const void *pa, const void *pb)
+ {
+ 	const struct pct_info *a = pa;
+ 	const struct pct_info *b = pb;
+ 	if (a->first_row == b->first_row)
+ 		return (a->second_row < b->second_row) ? -1 : (a->second_row == b->second_row) ? 0 : 1;
+ 	else
+ 		return (a->first_row < b->first_row) ? -1 : 1;
+ }
+ 
+ static struct pct_info *setup_pct_info(int num_percentiles,
+ 									   Datum *percentiles_datum,
+ 									   bool *percentiles_null,
+ 									   int64 rowcount,
+ 									   bool continuous)
+ {
+ 	struct pct_info *pct_info = palloc(num_percentiles * sizeof(struct pct_info));
+ 	int		i;
+ 
+ 	for (i = 0; i < num_percentiles; i++)
+ 	{
+ 		pct_info[i].idx = i;
+ 
+ 		if (percentiles_null[i])
+ 		{
+ 			pct_info[i].first_row = 0;
+ 			pct_info[i].second_row = 0;
+ 			pct_info[i].proportion = 0;
+ 		}
+ 		else
+ 		{
+ 			float8 p = DatumGetFloat8(percentiles_datum[i]);
+ 
+ 			if (p < 0 || p > 1 || isnan(p))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 						 errmsg("percentile value %g must be between 0 and 1", p)));
+ 
+ 			if (continuous)
+ 			{
+ 				pct_info[i].first_row = 1 + floor(p * (rowcount - 1));
+ 				pct_info[i].second_row = 1 + ceil(p * (rowcount - 1));
+ 				pct_info[i].proportion = (p * (rowcount-1)) - floor(p * (rowcount-1));
+ 			}
+ 			else
+ 			{
+ 				/*
+ 				 * We need the smallest K such that (K/N) >= percentile. K starts at 1.
+ 				 * Therefore K >= N*percentile
+ 				 * Therefore K = ceil(N*percentile), minimum 1
+ 				 */
+ 
+ 				pct_info[i].first_row = Max(1, (int64) ceil(rowcount * p));
+ 				pct_info[i].second_row = 0;
+ 				pct_info[i].proportion = 0;
+ 			}
+ 		}
+ 	}
+ 
+ 	qsort(pct_info, num_percentiles, sizeof(struct pct_info), pct_info_cmp);
+ 
+ 	return pct_info;
+ }
+ 
+ Datum
+ percentile_disc_multi_final(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *param;
+ 	Datum	   *percentiles_datum;
+ 	bool	   *percentiles_null;
+ 	int			num_percentiles;
+ 	int64		rowcount = AggSetGetRowCount(fcinfo);
+ 	int64		rownum = 0;
+ 	Tuplesortstate *sorter;
+ 	Oid			datumtype;
+ 	Datum		val;
+ 	bool		isnull;
+ 	Datum	   *result_datum;
+ 	bool	   *result_isnull;
+ 	int			i;
+ 	struct pct_info *pct_info;
+ 
+ 	struct mode_type_info {
+ 		Oid typid;
+ 		int16 typLen;
+ 		bool typByVal;
+ 		char typAlign;
+ 	} *typinfo = fcinfo->flinfo->fn_extra;
+ 
+ 	if (PG_ARGISNULL(0) || rowcount < 1)
+ 		PG_RETURN_NULL();
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ 
+ 	if (!typinfo || typinfo->typid != datumtype)
+ 	{
+ 		if (typinfo)
+ 			pfree(typinfo);
+ 		typinfo = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 									 sizeof(struct mode_type_info));
+ 		typinfo->typid = datumtype;
+ 		get_typlenbyvalalign(datumtype,
+ 							 &typinfo->typLen,
+ 							 &typinfo->typByVal,
+ 							 &typinfo->typAlign);
+ 	}
+ 
+ 	param = PG_GETARG_ARRAYTYPE_P(0);
+ 
+ 	deconstruct_array(param, FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd',
+ 					  &percentiles_datum, &percentiles_null, &num_percentiles);
+ 
+ 	if (num_percentiles == 0)
+ 		PG_RETURN_POINTER(construct_empty_array(datumtype));
+ 
+ 	result_datum = palloc0(num_percentiles * sizeof(Datum));
+ 	result_isnull = palloc0(num_percentiles * sizeof(bool));
+ 
+ 	pct_info = setup_pct_info(num_percentiles,
+ 							  percentiles_datum,
+ 							  percentiles_null,
+ 							  rowcount,
+ 							  false);
+ 
+ 	/*
+ 	 * Start by dealing with any nulls in the param array - those are
+ 	 * sorted to the front on row=0, so set the corresponding result
+ 	 * indexes to null
+ 	 */
+ 	for (i = 0; i < num_percentiles; ++i)
+ 	{
+ 		int idx = pct_info[i].idx;
+ 
+ 		if (pct_info[i].first_row > 0)
+ 			break;
+ 
+ 		result_datum[idx] = (Datum) 0;
+ 		result_isnull[idx] = true;
+ 	}
+ 
+ 	/*
+ 	 * If there's anything left after doing the nulls, then grind the
+ 	 * input and extract the needed values
+ 	 */
+ 	if (i < num_percentiles)
+ 	{
+ 		tuplesort_performsort(sorter);
+ 
+ 		for (; i < num_percentiles; ++i)
+ 		{
+ 			int64 target_row = pct_info[i].first_row;
+ 			int idx = pct_info[i].idx;
+ 
+ 			if (target_row > rownum)
+ 			{
+ 				while (target_row > ++rownum)
+ 				{
+ 					if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ 						elog(ERROR,"missing row in percentile_disc");
+ 				}
+ 
+ 				if (!tuplesort_getdatum(sorter, true, &val, &isnull))
+ 					elog(ERROR,"missing row in percentile_disc");
+ 			}
+ 
+ 			result_datum[idx] = val;
+ 			result_isnull[idx] = isnull;
+ 		}
+ 	}
+ 
+ 	/* We make the output array the same shape as the input */
+ 
+ 	PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull,
+ 										 ARR_NDIM(param),
+ 										 ARR_DIMS(param), ARR_LBOUND(param),
+ 										 datumtype,
+ 										 typinfo->typLen,
+ 										 typinfo->typByVal,
+ 										 typinfo->typAlign));
+ }
+ 
+ static Datum
+ percentile_cont_multi_final_common(FunctionCallInfo fcinfo,
+ 								   Oid expect_type,
+ 								   int16 typLen, bool typByVal, char typAlign,
+ 								   LerpFunc lerpfunc)
+ {
+ 	ArrayType  *param;
+ 	Datum	   *percentiles_datum;
+ 	bool	   *percentiles_null;
+ 	int			num_percentiles;
+ 	int64		rowcount = AggSetGetRowCount(fcinfo);
+ 	int64		rownum = 0;
+ 	int64		rownum_second = 0;
+ 	Tuplesortstate *sorter;
+ 	Oid			datumtype;
+ 	Datum		first_val;
+ 	Datum		second_val;
+ 	bool		isnull;
+ 	Datum	   *result_datum;
+ 	bool	   *result_isnull;
+ 	int			i;
+ 	struct pct_info *pct_info;
+ 
+ 	if (PG_ARGISNULL(0) || rowcount < 1)
+ 		PG_RETURN_NULL();
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ 	Assert(datumtype == expect_type);
+ 
+ 	param = PG_GETARG_ARRAYTYPE_P(0);
+ 
+ 	deconstruct_array(param, FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd',
+ 					  &percentiles_datum, &percentiles_null, &num_percentiles);
+ 
+ 	if (num_percentiles == 0)
+ 		PG_RETURN_POINTER(construct_empty_array(datumtype));
+ 
+ 	result_datum = palloc0(num_percentiles * sizeof(Datum));
+ 	result_isnull = palloc0(num_percentiles * sizeof(bool));
+ 
+ 	pct_info = setup_pct_info(num_percentiles,
+ 							  percentiles_datum,
+ 							  percentiles_null,
+ 							  rowcount,
+ 							  true);
+ 
+ 	/*
+ 	 * Start by dealing with any nulls in the param array - those are
+ 	 * sorted to the front on row=0, so set the corresponding result
+ 	 * indexes to null
+ 	 */
+ 	for (i = 0; i < num_percentiles; ++i)
+ 	{
+ 		int idx = pct_info[i].idx;
+ 
+ 		if (pct_info[i].first_row > 0)
+ 			break;
+ 
+ 		result_datum[idx] = (Datum) 0;
+ 		result_isnull[idx] = true;
+ 	}
+ 
+ 	/*
+ 	 * If there's anything left after doing the nulls, then grind the
+ 	 * input and extract the needed values
+ 	 */
+ 	if (i < num_percentiles)
+ 	{
+ 		tuplesort_performsort(sorter);
+ 
+ 		for (; i < num_percentiles; ++i)
+ 		{
+ 			int64 target_row = pct_info[i].first_row;
+ 			bool need_lerp = pct_info[i].second_row > target_row;
+ 			int idx = pct_info[i].idx;
+ 
+ 			if (target_row > rownum_second)
+ 			{
+ 				rownum = rownum_second;
+ 
+ 				while (target_row > ++rownum)
+ 				{
+ 					if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ 						elog(ERROR,"missing row in percentile_cont");
+ 				}
+ 
+ 				if (!tuplesort_getdatum(sorter, true, &first_val, &isnull) || isnull)
+ 					elog(ERROR,"missing row in percentile_cont");
+ 
+ 				rownum_second = rownum;
+ 
+ 				if (need_lerp)
+ 				{
+ 					if (!tuplesort_getdatum(sorter, true, &second_val, &isnull) || isnull)
+ 						elog(ERROR,"missing row in percentile_cont");
+ 					++rownum_second;
+ 				}
+ 			}
+ 			else if (target_row == rownum_second)
+ 			{
+ 				first_val = second_val;
+ 				rownum = rownum_second;
+ 
+ 				if (need_lerp)
+ 				{
+ 					if (!tuplesort_getdatum(sorter, true, &second_val, &isnull) || isnull)
+ 						elog(ERROR,"missing row in percentile_cont");
+ 					++rownum_second;
+ 				}
+ 			}
+ 
+ 			if (need_lerp)
+ 			{
+ 				result_datum[idx] = lerpfunc(first_val, second_val, pct_info[i].proportion);
+ 			}
+ 			else
+ 				result_datum[idx] = first_val;
+ 
+ 			result_isnull[idx] = false;
+ 		}
+ 	}
+ 
+ 	/* We make the output array the same shape as the input */
+ 
+ 	PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull,
+ 										 ARR_NDIM(param),
+ 										 ARR_DIMS(param), ARR_LBOUND(param),
+ 										 expect_type,
+ 										 typLen,
+ 										 typByVal,
+ 										 typAlign));
+ }
+ 
+ 
+ /*
+  * percentile_cont(float8[]) within group (float8)  - continuous percentiles
+  */
+ 
+ Datum percentile_cont_float8_multi_final(PG_FUNCTION_ARGS);
+ Datum percentile_cont_interval_multi_final(PG_FUNCTION_ARGS);
+ 
+ Datum
+ percentile_cont_float8_multi_final(PG_FUNCTION_ARGS)
+ {
+ 	return percentile_cont_multi_final_common(fcinfo,
+ 											  FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd',
+ 											  float8_lerp);
+ }
+ 
+ /*
+  * percentile_cont(float8[]) within group (Interval)  - continuous percentiles
+  */
+ 
+ Datum
+ percentile_cont_interval_multi_final(PG_FUNCTION_ARGS)
+ {
+ 	return percentile_cont_multi_final_common(fcinfo,
+ 											  INTERVALOID, 16, false, 'd',
+ 											  interval_lerp);
+ }
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 22,27 ****
--- 22,28 ----
  #include "access/sysattr.h"
  #include "catalog/dependency.h"
  #include "catalog/indexing.h"
+ #include "catalog/pg_aggregate.h"
  #include "catalog/pg_authid.h"
  #include "catalog/pg_collation.h"
  #include "catalog/pg_constraint.h"
***************
*** 293,298 **** static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
--- 294,302 ----
  static int print_function_arguments(StringInfo buf, HeapTuple proctup,
  						 bool print_table_args, bool print_defaults);
  static void print_function_rettype(StringInfo buf, HeapTuple proctup);
+ static void print_aggregate_arguments(StringInfo buf,
+ 									  HeapTuple proctup, HeapTuple aggtup,
+ 									  bool print_defaults);
  static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
  				 Bitmapset *rels_used);
  static bool refname_is_unique(char *refname, deparse_namespace *dpns,
***************
*** 403,408 **** static char *generate_function_name(Oid funcid, int nargs,
--- 407,414 ----
  static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
  static text *string_to_text(char *str);
  static char *flatten_reloptions(Oid relid);
+ static void get_aggstd_expr(Aggref *aggref, deparse_context *context);
+ static void get_ordset_expr(Aggref *aggref, deparse_context *context);
  
  #define only_marker(rte)  ((rte)->inh ? "" : "ONLY ")
  
***************
*** 2268,2273 **** print_function_arguments(StringInfo buf, HeapTuple proctup,
--- 2274,2422 ----
  
  
  /*
+  * pg_get_aggregate_arguments
+  *		Get a nicely-formatted list of arguments for an aggregate.
+  *		This is everything that would go after the function name
+  *		in CREATE AGGREGATE, _including_ the parens, because in the
+  *      case of ordered set funcs, we emit the WITHIN GROUP clause
+  *      too.
+  */
+ Datum
+ pg_get_aggregate_arguments(PG_FUNCTION_ARGS)
+ {
+ 	Oid			funcid = PG_GETARG_OID(0);
+ 	StringInfoData buf;
+ 	HeapTuple	proctup;
+ 	HeapTuple	aggtup;
+ 
+ 	initStringInfo(&buf);
+ 
+ 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ 	if (!HeapTupleIsValid(proctup))
+ 		elog(ERROR, "cache lookup failed for function %u", funcid);
+ 
+ 	aggtup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(funcid));
+ 	if (!HeapTupleIsValid(aggtup))
+ 		elog(ERROR, "function %u is not an aggregate function", funcid);
+ 
+ 	(void) print_aggregate_arguments(&buf, proctup, aggtup, true);
+ 
+ 	ReleaseSysCache(aggtup);
+ 	ReleaseSysCache(proctup);
+ 
+ 	PG_RETURN_TEXT_P(string_to_text(buf.data));
+ }
+ 
+ /*
+  * pg_get_aggregate_identity_arguments
+  *		Get a formatted list of arguments for an aggregate.
+  *		This is everything that would go after the function name in
+  *		ALTER AGGREGATE, etc.  In particular, don't print defaults.
+  *      Currently, this is identical to pg_get_aggregate_arguments,
+  *      but if we ever allow defaults that will change.
+  */
+ Datum
+ pg_get_aggregate_identity_arguments(PG_FUNCTION_ARGS)
+ {
+ 	Oid			funcid = PG_GETARG_OID(0);
+ 	StringInfoData buf;
+ 	HeapTuple	proctup;
+ 	HeapTuple	aggtup;
+ 
+ 	initStringInfo(&buf);
+ 
+ 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ 	if (!HeapTupleIsValid(proctup))
+ 		elog(ERROR, "cache lookup failed for function %u", funcid);
+ 
+ 	aggtup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(funcid));
+ 	if (!HeapTupleIsValid(aggtup))
+ 		elog(ERROR, "function %u is not an aggregate function", funcid);
+ 
+ 	(void) print_aggregate_arguments(&buf, proctup, aggtup, false);
+ 
+ 	ReleaseSysCache(aggtup);
+ 	ReleaseSysCache(proctup);
+ 
+ 	PG_RETURN_TEXT_P(string_to_text(buf.data));
+ }
+ 
+ 
+ /*
+  * Common code for pg_get_aggregate_arguments
+  * We print argument defaults only if print_defaults is true.
+  */
+ static void
+ print_aggregate_arguments(StringInfo buf,
+ 						  HeapTuple proctup, HeapTuple aggtup,
+ 						  bool print_defaults)
+ {
+ 	Form_pg_aggregate agg = (Form_pg_aggregate) GETSTRUCT(aggtup);
+ 	int			numargs;
+ 	bool        ordsetfunc = agg->aggisordsetfunc;
+ 	int         numdirectargs = agg->aggordnargs;
+ 	Oid		   *argtypes;
+ 	char	  **argnames;
+ 	char	   *argmodes;
+ 	int			i;
+ 
+ 	/* defaults not supported at this time */
+ 	(void) print_defaults;
+ 
+ 	numargs = get_func_arg_info(proctup,
+ 								&argtypes, &argnames, &argmodes);
+ 
+ 	appendStringInfoChar(buf, '(');
+ 
+ 	for (i = 0; i < numargs; i++)
+ 	{
+ 		Oid			argtype = argtypes[i];
+ 		char	   *argname = argnames ? argnames[i] : NULL;
+ 		char		argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
+ 		const char *modename;
+ 
+ 		switch (argmode)
+ 		{
+ 			case PROARGMODE_IN:
+ 				modename = "";
+ 				break;
+ 			case PROARGMODE_VARIADIC:
+ 				modename = "VARIADIC ";
+ 				break;
+ 			default:
+ 				elog(ERROR, "invalid parameter mode '%c'", argmode);
+ 				modename = NULL;	/* keep compiler quiet */
+ 				break;
+ 		}
+ 
+ 		if (i == numdirectargs)
+ 		{
+ 			appendStringInfoString(buf, ") WITHIN GROUP (");
+ 		}
+ 		else if (i > 0)
+ 			appendStringInfoString(buf, ", ");
+ 
+ 		appendStringInfoString(buf, modename);
+ 
+ 		if (argname && argname[0])
+ 			appendStringInfo(buf, "%s ", quote_identifier(argname));
+ 
+ 		appendStringInfoString(buf, format_type_be(argtype));
+ 	}
+ 
+ 	if (ordsetfunc)
+ 	{
+ 		if (numdirectargs < 0 || numdirectargs == numargs)
+ 			appendStringInfoString(buf, ") WITHIN GROUP (*");
+ 	}
+ 	else if (numargs == 0)
+ 		appendStringInfoChar(buf, '*');
+ 
+ 	appendStringInfoChar(buf, ')');
+ }
+ 
+ 
+ /*
   * deparse_expression			- General utility for deparsing expressions
   *
   * calls deparse_expression_pretty with all prettyPrinting disabled
***************
*** 7408,7413 **** static void
--- 7557,7636 ----
  get_agg_expr(Aggref *aggref, deparse_context *context)
  {
  	StringInfo	buf = context->buf;
+ 
+ 	if (aggref->isordset)
+ 	{
+ 		get_ordset_expr(aggref, context);
+ 	}
+ 	else
+ 	{
+ 		get_aggstd_expr(aggref, context);
+ 	}
+ 
+ 	if (aggref->aggfilter != NULL)
+ 	{
+ 		appendStringInfoString(buf, ") FILTER (WHERE ");
+ 		get_rule_expr((Node *)aggref->aggfilter, context, false);
+ 	}
+ 
+ 	appendStringInfoString(buf, ")");
+ }
+ 
+ static void
+ get_ordset_expr(Aggref *aggref, deparse_context *context)
+ {
+ 	StringInfo	buf = context->buf;
+ 	Oid			argtypes[FUNC_MAX_ARGS];
+ 	List	   *arglist;
+ 	int			nargs;
+ 	ListCell   *l;
+ 
+ 	arglist = NIL;
+ 	nargs = 0;
+ 
+ 	foreach(l, aggref->orddirectargs)
+ 	{
+ 		Node	   *arg = (Node *) lfirst(l);
+ 
+ 		Assert(!IsA(arg, NamedArgExpr));
+ 		if (nargs >= FUNC_MAX_ARGS)		/* paranoia */
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ 					 errmsg("too many arguments")));
+ 		argtypes[nargs] = exprType(arg);
+ 		nargs++;
+ 	}
+ 
+ 	/* For direct arguments in case of ordered set functions */
+ 	foreach(l, aggref->args)
+ 	{
+ 		TargetEntry *tle = (TargetEntry *) lfirst(l);
+ 		Node	   *arg = (Node *) tle->expr;
+ 
+ 		Assert(!IsA(arg, NamedArgExpr));
+ 		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(",
+ 					 generate_function_name(aggref->aggfnoid, nargs,
+ 											NIL, argtypes,
+ 											false, NULL));
+ 
+ 	get_rule_expr((Node *)aggref->orddirectargs, context, true);
+ 	appendStringInfoString(buf, ") WITHIN GROUP (ORDER BY ");
+ 	get_rule_orderby(aggref->aggorder, aggref->args, false, context);
+ 	
+ }
+ static void
+ get_aggstd_expr(Aggref *aggref, deparse_context *context)
+ {
+ 	StringInfo	buf = context->buf;
  	Oid			argtypes[FUNC_MAX_ARGS];
  	List	   *arglist;
  	int			nargs;
***************
*** 7462,7475 **** get_agg_expr(Aggref *aggref, deparse_context *context)
  		appendStringInfoString(buf, " ORDER BY ");
  		get_rule_orderby(aggref->aggorder, aggref->args, false, context);
  	}
- 
- 	if (aggref->aggfilter != NULL)
- 	{
- 		appendStringInfoString(buf, ") FILTER (WHERE ");
- 		get_rule_expr((Node *) aggref->aggfilter, context, false);
- 	}
- 
- 	appendStringInfoChar(buf, ')');
  }
  
  /*
--- 7685,7690 ----
*** a/src/backend/utils/sort/tuplesort.c
--- b/src/backend/utils/sort/tuplesort.c
***************
*** 1411,1433 **** tuplesort_performsort(Tuplesortstate *state)
   * Internal routine to fetch the next tuple in either forward or back
   * direction into *stup.  Returns FALSE if no more tuples.
   * If *should_free is set, the caller must pfree stup.tuple when done with it.
   */
  static bool
  tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  						  SortTuple *stup, bool *should_free)
  {
  	unsigned int tuplen;
  
  	switch (state->status)
  	{
  		case TSS_SORTEDINMEM:
  			Assert(forward || state->randomAccess);
! 			*should_free = false;
  			if (forward)
  			{
  				if (state->current < state->memtupcount)
  				{
! 					*stup = state->memtuples[state->current++];
  					return true;
  				}
  				state->eof_reached = true;
--- 1411,1439 ----
   * Internal routine to fetch the next tuple in either forward or back
   * direction into *stup.  Returns FALSE if no more tuples.
   * If *should_free is set, the caller must pfree stup.tuple when done with it.
+  * stup may be null to move without fetching.
   */
  static bool
  tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  						  SortTuple *stup, bool *should_free)
  {
  	unsigned int tuplen;
+ 	SortTuple dummy;
+ 	SortTuple *ptup = stup ? stup : &dummy;
  
  	switch (state->status)
  	{
  		case TSS_SORTEDINMEM:
  			Assert(forward || state->randomAccess);
! 			if (should_free)
! 				*should_free = false;
  			if (forward)
  			{
  				if (state->current < state->memtupcount)
  				{
! 					if (stup)
! 						*stup = state->memtuples[state->current];
! 					state->current++;
  					return true;
  				}
  				state->eof_reached = true;
***************
*** 1459,1479 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  					if (state->current <= 0)
  						return false;
  				}
! 				*stup = state->memtuples[state->current - 1];
  				return true;
  			}
  			break;
  
  		case TSS_SORTEDONTAPE:
  			Assert(forward || state->randomAccess);
! 			*should_free = true;
  			if (forward)
  			{
  				if (state->eof_reached)
  					return false;
  				if ((tuplen = getlen(state, state->result_tape, true)) != 0)
  				{
! 					READTUP(state, stup, state->result_tape, tuplen);
  					return true;
  				}
  				else
--- 1465,1489 ----
  					if (state->current <= 0)
  						return false;
  				}
! 				if (stup)
! 					*stup = state->memtuples[state->current - 1];
  				return true;
  			}
  			break;
  
  		case TSS_SORTEDONTAPE:
  			Assert(forward || state->randomAccess);
! 			if (should_free)
! 				*should_free = true;
  			if (forward)
  			{
  				if (state->eof_reached)
  					return false;
  				if ((tuplen = getlen(state, state->result_tape, true)) != 0)
  				{
! 					READTUP(state, ptup, state->result_tape, tuplen);
! 					if (!stup && dummy.tuple)
! 						pfree(dummy.tuple);
  					return true;
  				}
  				else
***************
*** 1546,1557 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  									  state->result_tape,
  									  tuplen))
  				elog(ERROR, "bogus tuple length in backward scan");
! 			READTUP(state, stup, state->result_tape, tuplen);
  			return true;
  
  		case TSS_FINALMERGE:
  			Assert(forward);
! 			*should_free = true;
  
  			/*
  			 * This code should match the inner loop of mergeonerun().
--- 1556,1570 ----
  									  state->result_tape,
  									  tuplen))
  				elog(ERROR, "bogus tuple length in backward scan");
! 			READTUP(state, ptup, state->result_tape, tuplen);
! 			if (!stup && dummy.tuple)
! 				pfree(dummy.tuple);
  			return true;
  
  		case TSS_FINALMERGE:
  			Assert(forward);
! 			if (should_free)
! 				*should_free = true;
  
  			/*
  			 * This code should match the inner loop of mergeonerun().
***************
*** 1563,1573 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  				int			tupIndex;
  				SortTuple  *newtup;
  
! 				*stup = state->memtuples[0];
  				/* returned tuple is no longer counted in our memory space */
! 				if (stup->tuple)
  				{
! 					tuplen = GetMemoryChunkSpace(stup->tuple);
  					state->availMem += tuplen;
  					state->mergeavailmem[srcTape] += tuplen;
  				}
--- 1576,1586 ----
  				int			tupIndex;
  				SortTuple  *newtup;
  
! 				*ptup = state->memtuples[0];
  				/* returned tuple is no longer counted in our memory space */
! 				if (ptup->tuple)
  				{
! 					tuplen = GetMemoryChunkSpace(ptup->tuple);
  					state->availMem += tuplen;
  					state->mergeavailmem[srcTape] += tuplen;
  				}
***************
*** 1598,1603 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
--- 1611,1618 ----
  				newtup->tupindex = state->mergefreelist;
  				state->mergefreelist = tupIndex;
  				state->mergeavailslots[srcTape]++;
+ 				if (!stup && dummy.tuple)
+ 					pfree(dummy.tuple);
  				return true;
  			}
  			return false;
***************
*** 1620,1639 **** tuplesort_gettupleslot(Tuplesortstate *state, bool forward,
  	MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext);
  	SortTuple	stup;
  	bool		should_free;
  
! 	if (!tuplesort_gettuple_common(state, forward, &stup, &should_free))
! 		stup.tuple = NULL;
  
  	MemoryContextSwitchTo(oldcontext);
  
! 	if (stup.tuple)
  	{
! 		ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free);
  		return true;
  	}
  	else
  	{
! 		ExecClearTuple(slot);
  		return false;
  	}
  }
--- 1635,1656 ----
  	MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext);
  	SortTuple	stup;
  	bool		should_free;
+ 	bool        found;
  
! 	found = tuplesort_gettuple_common(state, forward, (slot ? &stup : NULL), &should_free);
  
  	MemoryContextSwitchTo(oldcontext);
  
! 	if (found)
  	{
! 		if (slot)
! 			ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free);
  		return true;
  	}
  	else
  	{
! 		if (slot)
! 			ExecClearTuple(slot);
  		return false;
  	}
  }
***************
*** 1692,1715 **** tuplesort_getdatum(Tuplesortstate *state, bool forward,
  	SortTuple	stup;
  	bool		should_free;
  
! 	if (!tuplesort_gettuple_common(state, forward, &stup, &should_free))
  	{
  		MemoryContextSwitchTo(oldcontext);
  		return false;
  	}
  
! 	if (stup.isnull1 || state->datumTypeByVal)
  	{
! 		*val = stup.datum1;
! 		*isNull = stup.isnull1;
! 	}
! 	else
! 	{
! 		if (should_free)
  			*val = stup.datum1;
  		else
! 			*val = datumCopy(stup.datum1, false, state->datumTypeLen);
! 		*isNull = false;
  	}
  
  	MemoryContextSwitchTo(oldcontext);
--- 1709,1735 ----
  	SortTuple	stup;
  	bool		should_free;
  
! 	if (!tuplesort_gettuple_common(state, forward, (val ? &stup : NULL), &should_free))
  	{
  		MemoryContextSwitchTo(oldcontext);
  		return false;
  	}
  
! 	if (val)
  	{
! 		if (stup.isnull1 || state->datumTypeByVal)
! 		{
  			*val = stup.datum1;
+ 			*isNull = stup.isnull1;
+ 		}
  		else
! 		{
! 			if (should_free)
! 				*val = stup.datum1;
! 			else
! 				*val = datumCopy(stup.datum1, false, state->datumTypeLen);
! 			*isNull = false;
! 		}
  	}
  
  	MemoryContextSwitchTo(oldcontext);
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 229,234 **** static void getTableData(TableInfo *tblinfo, int numTables, bool oids);
--- 229,235 ----
  static void makeTableDataInfo(TableInfo *tbinfo, bool oids);
  static void buildMatViewRefreshDependencies(Archive *fout);
  static void getTableDataFKConstraints(void);
+ static char *format_aggregate_arguments(FuncInfo *finfo, char *funcargs);
  static char *format_function_arguments(FuncInfo *finfo, char *funcargs,
  									   bool is_agg);
  static char *format_function_arguments_old(Archive *fout,
***************
*** 9459,9464 **** dumpProcLang(Archive *fout, ProcLangInfo *plang)
--- 9460,9481 ----
  }
  
  /*
+  * format_aggregate_arguments: generate function name and argument list
+  *
+  * This is used when we can rely on pg_get_aggregate_arguments to format
+  * the argument list.
+  */
+ static char *
+ format_aggregate_arguments(FuncInfo *finfo, char *funcargs)
+ {
+ 	PQExpBufferData fn;
+ 
+ 	initPQExpBuffer(&fn);
+ 	appendPQExpBuffer(&fn, "%s%s", fmtId(finfo->dobj.name), funcargs);
+ 	return fn.data;
+ }
+ 
+ /*
   * format_function_arguments: generate function name and argument list
   *
   * This is used when we can rely on pg_get_function_arguments to format
***************
*** 11512,11517 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11529,11537 ----
  	int			i_aggtransfn;
  	int			i_aggfinalfn;
  	int			i_aggsortop;
+ 	int			i_aggtranssortop;
+ 	int			i_hypothetical;
+ 	int			i_isstrict;
  	int			i_aggtranstype;
  	int			i_aggtransspace;
  	int			i_agginitval;
***************
*** 11519,11528 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11539,11552 ----
  	const char *aggtransfn;
  	const char *aggfinalfn;
  	const char *aggsortop;
+ 	const char *aggtranssortop;
  	const char *aggtranstype;
  	const char *aggtransspace;
  	const char *agginitval;
+ 	bool        hypothetical;
+ 	bool        isstrict;
  	bool		convertok;
+ 	bool        has_comma = false;
  
  	/* Skip if not to be dumped */
  	if (!agginfo->aggfn.dobj.dump || dataOnly)
***************
*** 11543,11552 **** dumpAgg(Archive *fout, AggInfo *agginfo)
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "aggtransspace, agginitval, "
  						  "'t'::boolean AS convertok, "
! 						  "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs, "
! 						  "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs "
  					  "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
  						  "WHERE a.aggfnoid = p.oid "
  						  "AND p.oid = '%u'::pg_catalog.oid",
--- 11567,11579 ----
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
+ 						  "aggtranssortop::pg_catalog.regoperator, "
+ 						  "(aggordnargs = -2) as hypothetical, "
+ 						  "p.proisstrict as isstrict, "
  						  "aggtransspace, agginitval, "
  						  "'t'::boolean AS convertok, "
! 						  "pg_catalog.pg_get_aggregate_arguments(p.oid) AS funcargs, "
! 						  "pg_catalog.pg_get_aggregate_identity_arguments(p.oid) AS funciargs "
  					  "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
  						  "WHERE a.aggfnoid = p.oid "
  						  "AND p.oid = '%u'::pg_catalog.oid",
***************
*** 11557,11562 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11584,11592 ----
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
+ 						  "0 as aggtranssortop, "
+ 						  "false as hypothetical, "
+ 						  "false as isstrict, "
  						  "0 AS aggtransspace, agginitval, "
  						  "'t'::boolean AS convertok, "
  						  "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs, "
***************
*** 11571,11576 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11601,11609 ----
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
+ 						  "0 as aggtranssortop, "
+ 						  "false as hypothetical, "
+ 						  "false as isstrict, "
  						  "0 AS aggtransspace, agginitval, "
  						  "'t'::boolean AS convertok "
  						  "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
***************
*** 11583,11588 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11616,11624 ----
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "0 AS aggsortop, "
+ 						  "0 as aggtranssortop, "
+ 						  "'f'::boolean as hypothetical, "
+ 						  "'f'::boolean as isstrict, "
  						  "0 AS aggtransspace, agginitval, "
  						  "'t'::boolean AS convertok "
  					  "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
***************
*** 11595,11600 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11631,11639 ----
  		appendPQExpBuffer(query, "SELECT aggtransfn, aggfinalfn, "
  						  "format_type(aggtranstype, NULL) AS aggtranstype, "
  						  "0 AS aggsortop, "
+ 						  "0 as aggtranssortop, "
+ 						  "'f'::boolean as hypothetical, "
+ 						  "'f'::boolean as isstrict, "
  						  "0 AS aggtransspace, agginitval, "
  						  "'t'::boolean AS convertok "
  						  "FROM pg_aggregate "
***************
*** 11607,11612 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11646,11654 ----
  						  "aggfinalfn, "
  						  "(SELECT typname FROM pg_type WHERE oid = aggtranstype1) AS aggtranstype, "
  						  "0 AS aggsortop, "
+ 						  "0 as aggtranssortop, "
+ 						  "'f'::boolean as hypothetical, "
+ 						  "'f'::boolean as isstrict, "
  						  "0 AS aggtransspace, agginitval1 AS agginitval, "
  						  "(aggtransfn2 = 0 and aggtranstype2 = 0 and agginitval2 is null) AS convertok "
  						  "FROM pg_aggregate "
***************
*** 11619,11624 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11661,11669 ----
  	i_aggtransfn = PQfnumber(res, "aggtransfn");
  	i_aggfinalfn = PQfnumber(res, "aggfinalfn");
  	i_aggsortop = PQfnumber(res, "aggsortop");
+ 	i_aggtranssortop = PQfnumber(res, "aggtranssortop");
+ 	i_hypothetical = PQfnumber(res, "hypothetical");
+ 	i_isstrict = PQfnumber(res, "isstrict");
  	i_aggtranstype = PQfnumber(res, "aggtranstype");
  	i_aggtransspace = PQfnumber(res, "aggtransspace");
  	i_agginitval = PQfnumber(res, "agginitval");
***************
*** 11627,11638 **** dumpAgg(Archive *fout, AggInfo *agginfo)
  	aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
  	aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
  	aggsortop = PQgetvalue(res, 0, i_aggsortop);
  	aggtranstype = PQgetvalue(res, 0, i_aggtranstype);
  	aggtransspace = PQgetvalue(res, 0, i_aggtransspace);
  	agginitval = PQgetvalue(res, 0, i_agginitval);
  	convertok = (PQgetvalue(res, 0, i_convertok)[0] == 't');
  
! 	if (fout->remoteVersion >= 80400)
  	{
  		/* 8.4 or later; we rely on server-side code for most of the work */
  		char	   *funcargs;
--- 11672,11697 ----
  	aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
  	aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
  	aggsortop = PQgetvalue(res, 0, i_aggsortop);
+ 	aggtranssortop = PQgetvalue(res, 0, i_aggtranssortop);
+ 	hypothetical = (PQgetvalue(res, 0, i_hypothetical)[0] == 't');
+ 	isstrict = (PQgetvalue(res, 0, i_isstrict)[0] == 't');
  	aggtranstype = PQgetvalue(res, 0, i_aggtranstype);
  	aggtransspace = PQgetvalue(res, 0, i_aggtransspace);
  	agginitval = PQgetvalue(res, 0, i_agginitval);
  	convertok = (PQgetvalue(res, 0, i_convertok)[0] == 't');
  
! 	if (fout->remoteVersion >= 90400)
! 	{
! 		/* 9.4 or later; we rely on server-side code for almost all of the work */
! 		char	   *funcargs;
! 		char	   *funciargs;
! 
! 		funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs"));
! 		funciargs = PQgetvalue(res, 0, PQfnumber(res, "funciargs"));
! 		aggfullsig = format_aggregate_arguments(&agginfo->aggfn, funcargs);
! 		aggsig = format_aggregate_arguments(&agginfo->aggfn, funciargs);
! 	}
! 	else if (fout->remoteVersion >= 80400)
  	{
  		/* 8.4 or later; we rely on server-side code for most of the work */
  		char	   *funcargs;
***************
*** 11662,11705 **** dumpAgg(Archive *fout, AggInfo *agginfo)
  	if (fout->remoteVersion >= 70300)
  	{
  		/* If using 7.3's regproc or regtype, data is already quoted */
! 		appendPQExpBuffer(details, "    SFUNC = %s,\n    STYPE = %s",
! 						  aggtransfn,
! 						  aggtranstype);
  	}
  	else if (fout->remoteVersion >= 70100)
  	{
  		/* format_type quotes, regproc does not */
! 		appendPQExpBuffer(details, "    SFUNC = %s,\n    STYPE = %s",
  						  fmtId(aggtransfn),
  						  aggtranstype);
  	}
  	else
  	{
  		/* need quotes all around */
! 		appendPQExpBuffer(details, "    SFUNC = %s,\n",
  						  fmtId(aggtransfn));
  		appendPQExpBuffer(details, "    STYPE = %s",
  						  fmtId(aggtranstype));
  	}
  
  	if (strcmp(aggtransspace, "0") != 0)
  	{
  		appendPQExpBuffer(details, ",\n    SSPACE = %s",
  						  aggtransspace);
  	}
  
  	if (!PQgetisnull(res, 0, i_agginitval))
  	{
  		appendPQExpBufferStr(details, ",\n    INITCOND = ");
  		appendStringLiteralAH(details, agginitval, fout);
  	}
  
- 	if (strcmp(aggfinalfn, "-") != 0)
- 	{
- 		appendPQExpBuffer(details, ",\n    FINALFUNC = %s",
- 						  aggfinalfn);
- 	}
- 
  	aggsortop = convertOperatorReference(fout, aggsortop);
  	if (aggsortop)
  	{
--- 11721,11786 ----
  	if (fout->remoteVersion >= 70300)
  	{
  		/* If using 7.3's regproc or regtype, data is already quoted */
! 		/*
! 		 * either or both of SFUNC and STYPE might be missing in >90400,
! 		 * but if SFUNC is missing, then FINALFUNC will always be present,
! 		 * and if SFUNC is present then STYPE must also be present; the
! 		 * code below relies on these conditions to keep the commas in the
! 		 * right places. STRICT must be forced to false if SFUNC is present.
! 		 */
! 
! 		if (strcmp(aggtransfn,"-") != 0)
! 		{
! 			appendPQExpBuffer(details, "\n    SFUNC = %s,", aggtransfn);
! 			isstrict = false;
! 		}
! 
! 		if (strcmp(aggtranstype,"-") != 0)
! 			appendPQExpBuffer(details, "\n    STYPE = %s", aggtranstype);
! 		else
! 			has_comma = true;
  	}
  	else if (fout->remoteVersion >= 70100)
  	{
  		/* format_type quotes, regproc does not */
! 		appendPQExpBuffer(details, "\n    SFUNC = %s,\n    STYPE = %s",
  						  fmtId(aggtransfn),
  						  aggtranstype);
  	}
  	else
  	{
  		/* need quotes all around */
! 		appendPQExpBuffer(details, "\n    SFUNC = %s,\n",
  						  fmtId(aggtransfn));
  		appendPQExpBuffer(details, "    STYPE = %s",
  						  fmtId(aggtranstype));
  	}
  
+ 	if (strcmp(aggfinalfn, "-") != 0)
+ 	{
+ 		appendPQExpBuffer(details, "%s\n    FINALFUNC = %s",
+ 						  (has_comma ? "" : ","),
+ 						  aggfinalfn);
+ 	}
+ 
  	if (strcmp(aggtransspace, "0") != 0)
  	{
  		appendPQExpBuffer(details, ",\n    SSPACE = %s",
  						  aggtransspace);
  	}
  
+ 	if (hypothetical)
+ 		appendPQExpBufferStr(details, ",\n    HYPOTHETICAL");
+ 
+ 	if (isstrict)
+ 		appendPQExpBufferStr(details, ",\n    STRICT");
+ 
  	if (!PQgetisnull(res, 0, i_agginitval))
  	{
  		appendPQExpBufferStr(details, ",\n    INITCOND = ");
  		appendStringLiteralAH(details, agginitval, fout);
  	}
  
  	aggsortop = convertOperatorReference(fout, aggsortop);
  	if (aggsortop)
  	{
***************
*** 11707,11712 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11788,11800 ----
  						  aggsortop);
  	}
  
+ 	aggtranssortop = convertOperatorReference(fout, aggtranssortop);
+ 	if (aggtranssortop)
+ 	{
+ 		appendPQExpBuffer(details, ",\n    TRANSSORTOP = %s",
+ 						  aggtranssortop);
+ 	}
+ 
  	/*
  	 * DROP must be fully qualified in case same name appears in pg_catalog
  	 */
***************
*** 11714,11720 **** dumpAgg(Archive *fout, AggInfo *agginfo)
  					  fmtId(agginfo->aggfn.dobj.namespace->dobj.name),
  					  aggsig);
  
! 	appendPQExpBuffer(q, "CREATE AGGREGATE %s (\n%s\n);\n",
  					  aggfullsig, details->data);
  
  	appendPQExpBuffer(labelq, "AGGREGATE %s", aggsig);
--- 11802,11808 ----
  					  fmtId(agginfo->aggfn.dobj.namespace->dobj.name),
  					  aggsig);
  
! 	appendPQExpBuffer(q, "CREATE AGGREGATE %s (%s\n);\n",
  					  aggfullsig, details->data);
  
  	appendPQExpBuffer(labelq, "AGGREGATE %s", aggsig);
***************
*** 11743,11749 **** dumpAgg(Archive *fout, AggInfo *agginfo)
  	/*
  	 * Since there is no GRANT ON AGGREGATE syntax, we have to make the ACL
  	 * command look like a function's GRANT; in particular this affects the
! 	 * syntax for zero-argument aggregates.
  	 */
  	free(aggsig);
  	free(aggsig_tag);
--- 11831,11837 ----
  	/*
  	 * Since there is no GRANT ON AGGREGATE syntax, we have to make the ACL
  	 * command look like a function's GRANT; in particular this affects the
! 	 * syntax for zero-argument aggregates and ordered set functions.
  	 */
  	free(aggsig);
  	free(aggsig_tag);
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 72,78 **** describeAggregates(const char *pattern, bool verbose, bool showSystem)
  					  gettext_noop("Name"),
  					  gettext_noop("Result data type"));
  
! 	if (pset.sversion >= 80400)
  		appendPQExpBuffer(&buf,
  						  "  CASE WHEN p.pronargs = 0\n"
  						  "    THEN CAST('*' AS pg_catalog.text)\n"
--- 72,82 ----
  					  gettext_noop("Name"),
  					  gettext_noop("Result data type"));
  
! 	if (pset.sversion >= 90400)
! 		appendPQExpBuffer(&buf,
! 					      " pg_catalog.pg_get_aggregate_arguments(p.oid) AS \"%s\",\n",
! 						  gettext_noop("Argument data types"));
! 	else if (pset.sversion >= 80400)
  		appendPQExpBuffer(&buf,
  						  "  CASE WHEN p.pronargs = 0\n"
  						  "    THEN CAST('*' AS pg_catalog.text)\n"
***************
*** 254,260 **** describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
  					  gettext_noop("Schema"),
  					  gettext_noop("Name"));
  
! 	if (pset.sversion >= 80400)
  		appendPQExpBuffer(&buf,
  					"  pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n"
  				 "  pg_catalog.pg_get_function_arguments(p.oid) as \"%s\",\n"
--- 258,283 ----
  					  gettext_noop("Schema"),
  					  gettext_noop("Name"));
  
! 	if (pset.sversion >= 90400)
! 		appendPQExpBuffer(&buf,
! 					"  pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n"
! 				 "  CASE WHEN p.proisagg THEN pg_catalog.pg_get_aggregate_arguments(p.oid)\n"
!                  "       ELSE pg_catalog.pg_get_function_arguments(p.oid) END as \"%s\",\n"
! 						  " CASE\n"
! 						  "  WHEN p.proisagg THEN '%s'\n"
! 						  "  WHEN p.proiswindow THEN '%s'\n"
! 						  "  WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN '%s'\n"
! 						  "  ELSE '%s'\n"
! 						  " END as \"%s\"",
! 						  gettext_noop("Result data type"),
! 						  gettext_noop("Argument data types"),
! 		/* translator: "agg" is short for "aggregate" */
! 						  gettext_noop("agg"),
! 						  gettext_noop("window"),
! 						  gettext_noop("trigger"),
! 						  gettext_noop("normal"),
! 						  gettext_noop("Type"));
! 	else if (pset.sversion >= 80400)
  		appendPQExpBuffer(&buf,
  					"  pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n"
  				 "  pg_catalog.pg_get_function_arguments(p.oid) as \"%s\",\n"
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
***************
*** 33,38 ****
--- 33,41 ----
   *	aggsortop			associated sort operator (0 if none)
   *	aggtranstype		type of aggregate's transition (state) data
   *	aggtransspace		estimated size of state data (0 for default estimate)
+  *	aggtranssortop		An optional sort operator for the type aggtranstype
+  *	aggordnargs			Number of direct arguments to aggregate.
+  *	aggisordsetfunc		A flag to represent whether a function is ordered set or not
   *	agginitval			initial value for transition state (can be NULL)
   * ----------------------------------------------------------------
   */
***************
*** 46,51 **** CATALOG(pg_aggregate,2600) BKI_WITHOUT_OIDS
--- 49,57 ----
  	Oid			aggsortop;
  	Oid			aggtranstype;
  	int32		aggtransspace;
+ 	Oid			aggtranssortop;
+ 	int32		aggordnargs;
+ 	bool		aggisordsetfunc;
  
  #ifdef CATALOG_VARLEN			/* variable-length fields start here */
  	text		agginitval;
***************
*** 64,77 **** typedef FormData_pg_aggregate *Form_pg_aggregate;
   * ----------------
   */
  
! #define Natts_pg_aggregate				7
  #define Anum_pg_aggregate_aggfnoid		1
  #define Anum_pg_aggregate_aggtransfn	2
  #define Anum_pg_aggregate_aggfinalfn	3
  #define Anum_pg_aggregate_aggsortop		4
  #define Anum_pg_aggregate_aggtranstype	5
  #define Anum_pg_aggregate_aggtransspace 6
! #define Anum_pg_aggregate_agginitval	7
  
  
  /* ----------------
--- 70,86 ----
   * ----------------
   */
  
! #define Natts_pg_aggregate				10
  #define Anum_pg_aggregate_aggfnoid		1
  #define Anum_pg_aggregate_aggtransfn	2
  #define Anum_pg_aggregate_aggfinalfn	3
  #define Anum_pg_aggregate_aggsortop		4
  #define Anum_pg_aggregate_aggtranstype	5
  #define Anum_pg_aggregate_aggtransspace 6
! #define Anum_pg_aggregate_aggtranssortop	7
! #define Anum_pg_aggregate_aggordnargs	8
! #define Anum_pg_aggregate_aggisordsetfunc	9
! #define Anum_pg_aggregate_agginitval	10
  
  
  /* ----------------
***************
*** 80,242 **** typedef FormData_pg_aggregate *Form_pg_aggregate;
   */
  
  /* avg */
! DATA(insert ( 2100	int8_avg_accum	numeric_avg		0	2281	128	_null_ ));
! DATA(insert ( 2101	int4_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	int2_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	numeric_avg_accum	numeric_avg 0	2281	128	_null_ ));
! DATA(insert ( 2104	float4_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	float8_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	interval_accum	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	int8_avg_accum	numeric_sum		0	2281	128	_null_ ));
! DATA(insert ( 2108	int4_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2109	int2_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2110	float4pl		-				0	700		0	_null_ ));
! DATA(insert ( 2111	float8pl		-				0	701		0	_null_ ));
! DATA(insert ( 2112	cash_pl			-				0	790		0	_null_ ));
! DATA(insert ( 2113	interval_pl		-				0	1186	0	_null_ ));
! DATA(insert ( 2114	numeric_avg_accum	numeric_sum	0	2281	128	_null_ ));
  
  /* max */
! DATA(insert ( 2115	int8larger		-				413		20		0	_null_ ));
! DATA(insert ( 2116	int4larger		-				521		23		0	_null_ ));
! DATA(insert ( 2117	int2larger		-				520		21		0	_null_ ));
! DATA(insert ( 2118	oidlarger		-				610		26		0	_null_ ));
! DATA(insert ( 2119	float4larger	-				623		700		0	_null_ ));
! DATA(insert ( 2120	float8larger	-				674		701		0	_null_ ));
! DATA(insert ( 2121	int4larger		-				563		702		0	_null_ ));
! DATA(insert ( 2122	date_larger		-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	time_larger		-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	timetz_larger	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	cashlarger		-				903		790		0	_null_ ));
! DATA(insert ( 2126	timestamp_larger	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	timestamptz_larger	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	interval_larger -				1334	1186	0	_null_ ));
! DATA(insert ( 2129	text_larger		-				666		25		0	_null_ ));
! DATA(insert ( 2130	numeric_larger	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	array_larger	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	bpchar_larger	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	tidlarger		-				2800	27		0	_null_ ));
! DATA(insert ( 3526	enum_larger		-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	int8smaller		-				412		20		0	_null_ ));
! DATA(insert ( 2132	int4smaller		-				97		23		0	_null_ ));
! DATA(insert ( 2133	int2smaller		-				95		21		0	_null_ ));
! DATA(insert ( 2134	oidsmaller		-				609		26		0	_null_ ));
! DATA(insert ( 2135	float4smaller	-				622		700		0	_null_ ));
! DATA(insert ( 2136	float8smaller	-				672		701		0	_null_ ));
! DATA(insert ( 2137	int4smaller		-				562		702		0	_null_ ));
! DATA(insert ( 2138	date_smaller	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	time_smaller	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	timetz_smaller	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	cashsmaller		-				902		790		0	_null_ ));
! DATA(insert ( 2142	timestamp_smaller	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	timestamptz_smaller -			1322	1184	0	_null_ ));
! DATA(insert ( 2144	interval_smaller	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	text_smaller	-				664		25		0	_null_ ));
! DATA(insert ( 2146	numeric_smaller -				1754	1700	0	_null_ ));
! DATA(insert ( 2051	array_smaller	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	bpchar_smaller	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	tidsmaller		-				2799	27		0	_null_ ));
! DATA(insert ( 3527	enum_smaller	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	int8inc_any		-				0		20		0	"0" ));
! DATA(insert ( 2803	int8inc			-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	int8_accum	numeric_var_pop 0	2281	128	_null_ ));
! DATA(insert ( 2719	int4_accum	numeric_var_pop 0	2281	128	_null_ ));
! DATA(insert ( 2720	int2_accum	numeric_var_pop 0	2281	128	_null_ ));
! DATA(insert ( 2721	float4_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	float8_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	numeric_accum  numeric_var_pop 0	2281	128	_null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	int8_accum	numeric_var_samp	0	2281	128	_null_ ));
! DATA(insert ( 2642	int4_accum	numeric_var_samp	0	2281	128	_null_ ));
! DATA(insert ( 2643	int2_accum	numeric_var_samp	0	2281	128	_null_ ));
! DATA(insert ( 2644	float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	numeric_accum  numeric_var_samp 0	2281	128	_null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	int8_accum	numeric_var_samp	0	2281	128	_null_ ));
! DATA(insert ( 2149	int4_accum	numeric_var_samp	0	2281	128	_null_ ));
! DATA(insert ( 2150	int2_accum	numeric_var_samp	0	2281	128	_null_ ));
! DATA(insert ( 2151	float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	numeric_accum  numeric_var_samp 0	2281	128	_null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	int8_accum	numeric_stddev_pop		0	2281	128	_null_ ));
! DATA(insert ( 2725	int4_accum	numeric_stddev_pop		0	2281	128	_null_ ));
! DATA(insert ( 2726	int2_accum	numeric_stddev_pop		0	2281	128	_null_ ));
! DATA(insert ( 2727	float4_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	float8_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	numeric_accum	numeric_stddev_pop	0	2281	128	_null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	int8_accum	numeric_stddev_samp		0	2281	128	_null_ ));
! DATA(insert ( 2713	int4_accum	numeric_stddev_samp		0	2281	128	_null_ ));
! DATA(insert ( 2714	int2_accum	numeric_stddev_samp		0	2281	128	_null_ ));
! DATA(insert ( 2715	float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	numeric_accum	numeric_stddev_samp 0	2281	128	_null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	int8_accum	numeric_stddev_samp		0	2281	128	_null_ ));
! DATA(insert ( 2155	int4_accum	numeric_stddev_samp		0	2281	128	_null_ ));
! DATA(insert ( 2156	int2_accum	numeric_stddev_samp		0	2281	128	_null_ ));
! DATA(insert ( 2157	float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	numeric_accum	numeric_stddev_samp 0	2281	128	_null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	int8inc_float8_float8		-				0	20		0	"0" ));
! DATA(insert ( 2819	float8_regr_accum	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	float8_regr_accum	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	float8_regr_accum	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	float8_regr_accum	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	float8_regr_accum	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	float8_regr_accum	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	float8_regr_accum	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	float8_regr_accum	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	float8_regr_accum	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	float8_regr_accum	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	float8_regr_accum	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	booland_statefunc	-			58	16		0	_null_ ));
! DATA(insert ( 2518	boolor_statefunc	-			59	16		0	_null_ ));
! DATA(insert ( 2519	booland_statefunc	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236 int2and		  -					0	21		0	_null_ ));
! DATA(insert ( 2237 int2or		  -					0	21		0	_null_ ));
! DATA(insert ( 2238 int4and		  -					0	23		0	_null_ ));
! DATA(insert ( 2239 int4or		  -					0	23		0	_null_ ));
! DATA(insert ( 2240 int8and		  -					0	20		0	_null_ ));
! DATA(insert ( 2241 int8or		  -					0	20		0	_null_ ));
! DATA(insert ( 2242 bitand		  -					0	1560	0	_null_ ));
! DATA(insert ( 2243 bitor		  -					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901 xmlconcat2	  -					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	array_agg_transfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	string_agg_transfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	bytea_string_agg_transfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	json_agg_transfn	json_agg_finalfn	0	2281	0	_null_ ));
  
  /*
   * prototypes for functions in pg_aggregate.c
--- 89,264 ----
   */
  
  /* avg */
! DATA(insert ( 2100	int8_avg_accum	numeric_avg		0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2101	int4_avg_accum	int8_avg		0	1016	0	0 -1 f "{0,0}" ));
! DATA(insert ( 2102	int2_avg_accum	int8_avg		0	1016	0	0 -1 f "{0,0}" ));
! DATA(insert ( 2103	numeric_avg_accum	numeric_avg	0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2104	float4_accum	float8_avg		0	1022	0	0 -1 f "{0,0,0}" ));
! DATA(insert ( 2105	float8_accum	float8_avg		0	1022	0	0 -1 f "{0,0,0}" ));
! DATA(insert ( 2106	interval_accum	interval_avg	0	1187	0	0 -1 f "{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	int8_avg_accum	numeric_sum		0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2108	int4_sum		-				0	20		0	0 -1 f _null_ ));
! DATA(insert ( 2109	int2_sum		-				0	20		0	0 -1 f _null_ ));
! DATA(insert ( 2110	float4pl		-				0	700		0	0 -1 f _null_ ));
! DATA(insert ( 2111	float8pl		-				0	701		0	0 -1 f _null_ ));
! DATA(insert ( 2112	cash_pl			-				0	790		0	0 -1 f _null_ ));
! DATA(insert ( 2113	interval_pl		-				0	1186	0	0 -1 f _null_ ));
! DATA(insert ( 2114	numeric_avg_accum	numeric_sum	0	2281	128	0 -1 f _null_ ));
  
  /* max */
! DATA(insert ( 2115	int8larger		-				413		20		0	0 -1 f _null_ ));
! DATA(insert ( 2116	int4larger		-				521		23		0	0 -1 f _null_ ));
! DATA(insert ( 2117	int2larger		-				520		21		0	0 -1 f _null_ ));
! DATA(insert ( 2118	oidlarger		-				610		26		0	0 -1 f _null_ ));
! DATA(insert ( 2119	float4larger	-				623		700		0	0 -1 f _null_ ));
! DATA(insert ( 2120	float8larger	-				674		701		0	0 -1 f _null_ ));
! DATA(insert ( 2121	int4larger		-				563		702		0	0 -1 f _null_ ));
! DATA(insert ( 2122	date_larger		-				1097	1082	0	0 -1 f _null_ ));
! DATA(insert ( 2123	time_larger		-				1112	1083	0	0 -1 f _null_ ));
! DATA(insert ( 2124	timetz_larger	-				1554	1266	0	0 -1 f _null_ ));
! DATA(insert ( 2125	cashlarger		-				903		790		0	0 -1 f _null_ ));
! DATA(insert ( 2126	timestamp_larger	-			2064	1114	0	0 -1 f _null_ ));
! DATA(insert ( 2127	timestamptz_larger	-			1324	1184	0	0 -1 f _null_ ));
! DATA(insert ( 2128	interval_larger -				1334	1186	0	0 -1 f _null_ ));
! DATA(insert ( 2129	text_larger		-				666		25		0	0 -1 f _null_ ));
! DATA(insert ( 2130	numeric_larger	-				1756	1700	0	0 -1 f _null_ ));
! DATA(insert ( 2050	array_larger	-				1073	2277	0	0 -1 f _null_ ));
! DATA(insert ( 2244	bpchar_larger	-				1060	1042	0	0 -1 f _null_ ));
! DATA(insert ( 2797	tidlarger		-				2800	27		0	0 -1 f _null_ ));
! DATA(insert ( 3526	enum_larger		-				3519	3500	0	0 -1 f _null_ ));
  
  /* min */
! DATA(insert ( 2131	int8smaller		-				412		20		0	0 -1 f _null_ ));
! DATA(insert ( 2132	int4smaller		-				97		23		0	0 -1 f _null_ ));
! DATA(insert ( 2133	int2smaller		-				95		21		0	0 -1 f _null_ ));
! DATA(insert ( 2134	oidsmaller		-				609		26		0	0 -1 f _null_ ));
! DATA(insert ( 2135	float4smaller	-				622		700		0	0 -1 f _null_ ));
! DATA(insert ( 2136	float8smaller	-				672		701		0	0 -1 f _null_ ));
! DATA(insert ( 2137	int4smaller		-				562		702		0	0 -1 f _null_ ));
! DATA(insert ( 2138	date_smaller	-				1095	1082	0	0 -1 f _null_ ));
! DATA(insert ( 2139	time_smaller	-				1110	1083	0	0 -1 f _null_ ));
! DATA(insert ( 2140	timetz_smaller	-				1552	1266	0	0 -1 f _null_ ));
! DATA(insert ( 2141	cashsmaller		-				902		790		0	0 -1 f _null_ ));
! DATA(insert ( 2142	timestamp_smaller	-			2062	1114	0	0 -1 f _null_ ));
! DATA(insert ( 2143	timestamptz_smaller -			1322	1184	0	0 -1 f _null_ ));
! DATA(insert ( 2144	interval_smaller	-			1332	1186	0	0 -1 f _null_ ));
! DATA(insert ( 2145	text_smaller	-				664		25		0	0 -1 f _null_ ));
! DATA(insert ( 2146	numeric_smaller -				1754	1700	0	0 -1 f _null_ ));
! DATA(insert ( 2051	array_smaller	-				1072	2277	0	0 -1 f _null_ ));
! DATA(insert ( 2245	bpchar_smaller	-				1058	1042	0	0 -1 f _null_ ));
! DATA(insert ( 2798	tidsmaller		-				2799	27		0	0 -1 f _null_ ));
! DATA(insert ( 3527	enum_smaller	-				3518	3500	0	0 -1 f _null_ ));
  
  /* count */
! DATA(insert ( 2147	int8inc_any		-				0		20		0	0 -1 f "0" ));
! DATA(insert ( 2803	int8inc			-				0		20		0	0 -1 f "0" ));
  
  /* var_pop */
! DATA(insert ( 2718	int8_accum	numeric_var_pop 	0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2719	int4_accum	numeric_var_pop 	0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2720	int2_accum	numeric_var_pop 	0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2721	float4_accum	float8_var_pop	0	1022	0	0 -1 f "{0,0,0}" ));
! DATA(insert ( 2722	float8_accum	float8_var_pop	0	1022	0	0 -1 f "{0,0,0}" ));
! DATA(insert ( 2723	numeric_accum  numeric_var_pop	0	2281	128	0 -1 f _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	int8_accum	numeric_var_samp	0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2642	int4_accum	numeric_var_samp	0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2643	int2_accum	numeric_var_samp	0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2644	float4_accum	float8_var_samp 0	1022	0	0 -1 f "{0,0,0}" ));
! DATA(insert ( 2645	float8_accum	float8_var_samp 0	1022	0	0 -1 f "{0,0,0}" ));
! DATA(insert ( 2646	numeric_accum  numeric_var_samp 0	2281	128	0 -1 f _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	int8_accum	numeric_var_samp	0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2149	int4_accum	numeric_var_samp	0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2150	int2_accum	numeric_var_samp	0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2151	float4_accum	float8_var_samp 0	1022	0	0 -1 f "{0,0,0}" ));
! DATA(insert ( 2152	float8_accum	float8_var_samp 0	1022	0	0 -1 f "{0,0,0}" ));
! DATA(insert ( 2153	numeric_accum  numeric_var_samp 0	2281	128	0 -1 f _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	int8_accum	numeric_stddev_pop		0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2725	int4_accum	numeric_stddev_pop		0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2726	int2_accum	numeric_stddev_pop		0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2727	float4_accum	float8_stddev_pop	0	1022	0	0 -1 f "{0,0,0}" ));
! DATA(insert ( 2728	float8_accum	float8_stddev_pop	0	1022	0	0 -1 f "{0,0,0}" ));
! DATA(insert ( 2729	numeric_accum	numeric_stddev_pop	0	2281	128	0 -1 f _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	int8_accum	numeric_stddev_samp		0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2713	int4_accum	numeric_stddev_samp		0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2714	int2_accum	numeric_stddev_samp		0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2715	float4_accum	float8_stddev_samp	0	1022	0	0 -1 f "{0,0,0}" ));
! DATA(insert ( 2716	float8_accum	float8_stddev_samp	0	1022	0	0 -1 f "{0,0,0}" ));
! DATA(insert ( 2717	numeric_accum	numeric_stddev_samp 0	2281	128	0 -1 f _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	int8_accum	numeric_stddev_samp		0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2155	int4_accum	numeric_stddev_samp		0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2156	int2_accum	numeric_stddev_samp		0	2281	128	0 -1 f _null_ ));
! DATA(insert ( 2157	float4_accum	float8_stddev_samp	0	1022	0	0 -1 f "{0,0,0}" ));
! DATA(insert ( 2158	float8_accum	float8_stddev_samp	0	1022	0	0 -1 f "{0,0,0}" ));
! DATA(insert ( 2159	numeric_accum	numeric_stddev_samp 0	2281	128	0 -1 f _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	int8inc_float8_float8		-				0	20		0	0 -1 f "0" ));
! DATA(insert ( 2819	float8_regr_accum	float8_regr_sxx			0	1022	0	0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	float8_regr_accum	float8_regr_syy			0	1022	0	0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	float8_regr_accum	float8_regr_sxy			0	1022	0	0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	float8_regr_accum	float8_regr_avgx		0	1022	0	0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	float8_regr_accum	float8_regr_avgy		0	1022	0	0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	float8_regr_accum	float8_regr_r2			0	1022	0	0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	float8_regr_accum	float8_regr_slope		0	1022	0	0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	float8_regr_accum	float8_regr_intercept	0	1022	0	0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	float8_regr_accum	float8_covar_pop		0	1022	0	0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	float8_regr_accum	float8_covar_samp		0	1022	0	0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	float8_regr_accum	float8_corr				0	1022	0	0 -1 f "{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	booland_statefunc	-			58	16	0	0 -1 f _null_ ));
! DATA(insert ( 2518	boolor_statefunc	-			59	16	0	0 -1 f _null_ ));
! DATA(insert ( 2519	booland_statefunc	-			58	16	0	0 -1 f _null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236 int2and		  -					0	21		0	0 -1 f _null_ ));
! DATA(insert ( 2237 int2or		  -					0	21		0	0 -1 f _null_ ));
! DATA(insert ( 2238 int4and		  -					0	23		0	0 -1 f _null_ ));
! DATA(insert ( 2239 int4or		  -					0	23		0	0 -1 f _null_ ));
! DATA(insert ( 2240 int8and		  -					0	20		0	0 -1 f _null_ ));
! DATA(insert ( 2241 int8or		  -					0	20		0	0 -1 f _null_ ));
! DATA(insert ( 2242 bitand		  -					0	1560	0	0 -1 f _null_ ));
! DATA(insert ( 2243 bitor		  -					0	1560	0	0 -1 f _null_ ));
  
  /* xml */
! DATA(insert ( 2901 xmlconcat2	  -					0	142		0	0 -1 f _null_ ));
  
  /* array */
! DATA(insert ( 2335	array_agg_transfn	array_agg_finalfn		0	2281	0	0 -1 f _null_ ));
  
  /* text */
! DATA(insert ( 3538	string_agg_transfn	string_agg_finalfn		0	2281	0	0 -1 f _null_ ));
  
  /* bytea */
! DATA(insert ( 3545	bytea_string_agg_transfn	bytea_string_agg_finalfn	0	2281	0	0 -1 f _null_ ));
  
  /* json */
! DATA(insert ( 3175	json_agg_transfn	json_agg_finalfn		0	2281	0	0 -1 f _null_ ));
! 
! /* ordered set functions */
! DATA(insert ( 3931 	- 			percentile_disc_final				0 	0 	0	0	1 t _null_));
! DATA(insert ( 3935 	- 			percentile_cont_float8_final		0 	0 	0	0	1 t _null_));
! DATA(insert ( 3939 	- 			percentile_cont_interval_final		0 	0 	0	0	1 t _null_));
! DATA(insert ( 3920 	- 			rank_final							0 	16 	0	59 -2 t "f"));
! DATA(insert ( 3970 	- 			dense_rank_final					0 	16 	0	59 -2 t "f"));
! DATA(insert ( 3972 	- 			percent_rank_final					0 	16 	0	59 -2 t "f"));
! DATA(insert ( 3974 	- 			cume_dist_final						0 	16 	0	58 -2 t "f"));
! DATA(insert ( 3976 	- 			mode_final							0 	0 	0	0	0 t _null_));
! DATA(insert ( 3978 	- 			percentile_disc_multi_final				0 	0 	0	0	1 t _null_));
! DATA(insert ( 3980 	- 			percentile_cont_float8_multi_final		0 	0 	0	0	1 t _null_));
! DATA(insert ( 3982 	- 			percentile_cont_interval_multi_final	0 	0 	0	0	1 t _null_));
  
  /*
   * prototypes for functions in pg_aggregate.c
***************
*** 244,249 **** DATA(insert ( 3175	json_agg_transfn	json_agg_finalfn	0	2281	0	_null_ ));
--- 266,272 ----
  extern Oid AggregateCreate(const char *aggName,
  				Oid aggNamespace,
  				int numArgs,
+ 				int numDirectArgs,
  				oidvector *parameterTypes,
  				Datum allParameterTypes,
  				Datum parameterModes,
***************
*** 252,259 **** extern Oid AggregateCreate(const char *aggName,
  				List *aggtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
  				int32 aggTransSpace,
! 				const char *agginitval);
  
  #endif   /* PG_AGGREGATE_H */
--- 275,286 ----
  				List *aggtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
+ 				List *aggtranssortopName,
  				Oid aggTransType,
  				int32 aggTransSpace,
! 				const char *agginitval,
! 				bool isStrict,
! 				bool isOrderedSetFunc,
! 				bool isHypotheticalSet);
  
  #endif   /* PG_AGGREGATE_H */
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 1973,1978 **** DATA(insert OID = 2232 (  pg_get_function_identity_arguments	   PGNSP PGUID 12 1
--- 1973,1982 ----
  DESCR("identity argument list of a function");
  DATA(insert OID = 2165 (  pg_get_function_result	   PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ pg_get_function_result _null_ _null_ _null_ ));
  DESCR("result type of a function");
+ DATA(insert OID = 3179 (  pg_get_aggregate_arguments    PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ pg_get_aggregate_arguments _null_ _null_ _null_ ));
+ DESCR("argument list of an aggregate function");
+ DATA(insert OID = 3180 (  pg_get_aggregate_identity_arguments	   PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ pg_get_aggregate_identity_arguments _null_ _null_ _null_ ));
+ DESCR("identity argument list of an aggregate function");
  
  DATA(insert OID = 1686 (  pg_get_keywords		PGNSP PGUID 12 10 400 0 0 f f f f t t s 0 0 2249 "" "{25,18,25}" "{o,o,o}" "{word,catcode,catdesc}" _null_ pg_get_keywords _null_ _null_ _null_ ));
  DESCR("list of SQL keywords");
***************
*** 4758,4763 **** DESCR("SP-GiST support for quad tree over range");
--- 4762,4835 ----
  /* event triggers */
  DATA(insert OID = 3566 (  pg_event_trigger_dropped_objects		PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25}" "{o,o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, schema_name, object_name, object_identity}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
  DESCR("list objects dropped by the current command");
+ 
+ /* inverse distribution functions */
+ DATA(insert OID = 3931 ( percentile_disc	PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 2283 "701 2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("discrete percentile");
+ 
+ DATA(insert OID = 3932 ( percentile_disc_final	PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2283 "701 2283" _null_ _null_ _null_ _null_ percentile_disc_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3935 ( percentile_cont	PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("continous distribution percentile for float8");
+ 
+ DATA(insert OID = 3936 ( percentile_cont_float8_final	PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 701 "701 701" _null_ _null_ _null_ _null_ percentile_cont_float8_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3939 ( percentile_cont	PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 1186 "701 1186" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("continous distribution percentile for interval");
+ 
+ DATA(insert OID = 3940 ( percentile_cont_interval_final	PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1186 "701 1186" _null_ _null_ _null_ _null_ percentile_cont_interval_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ /* hypothetical set functions */
+ DATA(insert OID = 3920 ( rank		PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_	aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("hypothetical rank");
+ 
+ DATA(insert OID = 3969 ( rank_final		PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_	hypothetical_rank_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3970 ( dense_rank		PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_	aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("rank of hypothetical row without gaps");
+ 
+ DATA(insert OID = 3971 ( dense_rank_final		PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_	hypothetical_dense_rank_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3972 ( percent_rank		PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_	aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("fractional ranking of hypothetical row within a group");
+ 
+ DATA(insert OID = 3973 ( percent_rank_final		PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_	hypothetical_percent_rank_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3974 ( cume_dist		PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_	aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("cumulative distribution of hypothetical row in a group");
+ 
+ DATA(insert OID = 3975 ( cume_dist_final		PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_	hypothetical_cume_dist_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3976 ( mode		PGNSP PGUID 12 1 0 0 0 t f f f t f i 1 0 2283 2283 _null_ _null_ _null_ _null_	aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("most common value in group");
+ DATA(insert OID = 3977 ( mode_final		PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2283 2283 _null_ _null_ _null_ _null_	mode_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3978 ( percentile_disc	PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 2277 "1022 2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("multiple discrete percentiles");
+ 
+ DATA(insert OID = 3979 ( percentile_disc_multi_final	PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2277 "1022 2283" _null_ _null_ _null_ _null_ percentile_disc_multi_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3980 ( percentile_cont	PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 1022 "1022 701" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("multiple continuous percentiles of float8 values");
+ 
+ DATA(insert OID = 3981 ( percentile_cont_float8_multi_final	PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1022 "1022 701" _null_ _null_ _null_ _null_ percentile_cont_float8_multi_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3982 ( percentile_cont	PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 1187 "1022 1186" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("multiple continuous percentiles of interval values");
+ 
+ DATA(insert OID = 3983 ( percentile_cont_interval_multi_final	PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1187 "1022 1186" _null_ _null_ _null_ _null_ percentile_cont_interval_multi_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
  /*
   * Symbolic values for provolatile column: these indicate whether the result
   * of a function is dependent *only* on the values of its explicit arguments,
*** a/src/include/fmgr.h
--- b/src/include/fmgr.h
***************
*** 651,656 **** extern void **find_rendezvous_variable(const char *varName);
--- 651,685 ----
  extern int AggCheckCallContext(FunctionCallInfo fcinfo,
  					MemoryContext *aggcontext);
  
+ typedef struct Tuplesortstate fmTuplesortstate;
+ typedef struct tupleDesc *fmTupleDesc;
+ typedef struct TupleTableSlot fmTupleTableSlot;
+ 
+ extern int64 AggSetGetRowCount(FunctionCallInfo fcinfo);
+ 
+ extern void AggSetGetSortInfo(FunctionCallInfo fcinfo,
+ 							  fmTuplesortstate **sortstate,
+ 							  fmTupleDesc *tupdesc,
+ 							  fmTupleTableSlot **tupslot,
+ 							  Oid *datumtype);
+ 
+ /* int16 rather than AttrNumber here to avoid includes */
+ extern int AggSetGetDistinctInfo(FunctionCallInfo fcinfo,
+ 								 fmTupleTableSlot **tupslot,
+ 								 int16 **sortColIdx,
+ 								 FmgrInfo **equalfns);
+ 
+ /* int16 rather than AttrNumber here to avoid includes */
+ extern int AggSetGetSortOperators(FunctionCallInfo fcinfo,
+ 								  int16 **sortColIdx,
+ 								  Oid **sortOperators,
+ 								  Oid **sortEqOperators,
+ 								  Oid **sortCollations,
+ 								  bool **sortNullsFirst);
+ 
+ extern void AggSetGetPerTupleContext(FunctionCallInfo fcinfo,
+ 						 MemoryContext *memcontext);
+ 
  /*
   * We allow plugin modules to hook function entry/exit.  This is intended
   * as support for loadable security policy modules, which may want to
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 588,593 **** typedef struct AggrefExprState
--- 588,594 ----
  {
  	ExprState	xprstate;
  	List	   *args;			/* states of argument expressions */
+ 	List	   *orddirectargs;	/* Ordered direct arguments */
  	ExprState  *aggfilter;		/* FILTER expression */
  	int			aggno;			/* ID number for agg within its plan node */
  } AggrefExprState;
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 426,432 **** typedef enum NodeTag
  	T_WindowObjectData,			/* private in nodeWindowAgg.c */
  	T_TIDBitmap,				/* in nodes/tidbitmap.h */
  	T_InlineCodeBlock,			/* in nodes/parsenodes.h */
! 	T_FdwRoutine				/* in foreign/fdwapi.h */
  } NodeTag;
  
  /*
--- 426,433 ----
  	T_WindowObjectData,			/* private in nodeWindowAgg.c */
  	T_TIDBitmap,				/* in nodes/tidbitmap.h */
  	T_InlineCodeBlock,			/* in nodes/parsenodes.h */
! 	T_FdwRoutine,				/* in foreign/fdwapi.h */
! 	T_AggStatePerAggData			/* private in nodeAgg.c */
  } NodeTag;
  
  /*
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 303,308 **** typedef struct FuncCall
--- 303,309 ----
  	bool		agg_star;		/* argument was really '*' */
  	bool		agg_distinct;	/* arguments were labeled DISTINCT */
  	bool		func_variadic;	/* last argument was labeled VARIADIC */
+ 	bool		has_within_group;	/* WITHIN GROUP clause,if any */
  	struct WindowDef *over;		/* OVER clause, if any */
  	int			location;		/* token location, or -1 if unknown */
  } FuncCall;
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 247,255 **** typedef struct Aggref
--- 247,258 ----
  	List	   *args;			/* arguments and sort expressions */
  	List	   *aggorder;		/* ORDER BY (list of SortGroupClause) */
  	List	   *aggdistinct;	/* DISTINCT (list of SortGroupClause) */
+ 	List	   *orddirectargs;	/* Direct arguments for ordered set functions */
  	Expr	   *aggfilter;		/* FILTER expression */
  	bool		aggstar;		/* TRUE if argument list was really '*' */
  	bool		aggvariadic;	/* TRUE if VARIADIC was used in call */
+ 	bool		isordset;	/* If node is from an ordered set function */
+ 	bool		ishypothetical;	/* If node is from a hypothetical set function */
  	Index		agglevelsup;	/* > 0 if agg belongs to outer query */
  	int			location;		/* token location, or -1 if unknown */
  } Aggref;
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 412,417 **** PG_KEYWORD("where", WHERE, RESERVED_KEYWORD)
--- 412,418 ----
  PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("window", WINDOW, RESERVED_KEYWORD)
  PG_KEYWORD("with", WITH, RESERVED_KEYWORD)
+ PG_KEYWORD("within", WITHIN, UNRESERVED_KEYWORD)
  PG_KEYWORD("without", WITHOUT, UNRESERVED_KEYWORD)
  PG_KEYWORD("work", WORK, UNRESERVED_KEYWORD)
  PG_KEYWORD("wrapper", WRAPPER, UNRESERVED_KEYWORD)
*** a/src/include/parser/parse_agg.h
--- b/src/include/parser/parse_agg.h
***************
*** 17,23 ****
  
  extern void transformAggregateCall(ParseState *pstate, Aggref *agg,
  					   List *args, List *aggorder,
! 					   bool agg_distinct);
  extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
  						WindowDef *windef);
  
--- 17,23 ----
  
  extern void transformAggregateCall(ParseState *pstate, Aggref *agg,
  					   List *args, List *aggorder,
! 					   bool agg_distinct, bool agg_within_group);
  extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
  						WindowDef *windef);
  
***************
*** 34,37 **** extern void build_aggregate_fnexprs(Oid *agg_input_types,
--- 34,51 ----
  						Expr **transfnexpr,
  						Expr **finalfnexpr);
  
+ void
+ build_orderedset_fnexprs(Oid *agg_input_types,
+ 					int agg_num_inputs,
+ 					bool agg_variadic,
+ 					Oid agg_result_type,
+ 					Oid agg_input_collation,
+ 					Oid *agg_input_collation_array,
+ 					Oid finalfn_oid,
+ 					Expr **finalfnexpr);
+ 
+ int get_aggregate_argtypes(Aggref *aggref, 
+ 				Oid *inputTypes, 
+ 				Oid *inputCollations);
+ 
  #endif   /* PARSE_AGG_H */
*** a/src/include/parser/parse_clause.h
--- b/src/include/parser/parse_clause.h
***************
*** 31,37 **** extern List *transformGroupClause(ParseState *pstate, List *grouplist,
  					 ParseExprKind exprKind, bool useSQL99);
  extern List *transformSortClause(ParseState *pstate, List *orderlist,
  					List **targetlist, ParseExprKind exprKind,
! 					bool resolveUnknown, bool useSQL99);
  
  extern List *transformWindowDefinitions(ParseState *pstate,
  						   List *windowdefs,
--- 31,37 ----
  					 ParseExprKind exprKind, bool useSQL99);
  extern List *transformSortClause(ParseState *pstate, List *orderlist,
  					List **targetlist, ParseExprKind exprKind,
! 					bool resolveUnknown, bool useSQL99, bool keepDuplicates);
  
  extern List *transformWindowDefinitions(ParseState *pstate,
  						   List *windowdefs,
*** a/src/include/parser/parse_func.h
--- b/src/include/parser/parse_func.h
***************
*** 38,51 **** typedef enum
  	FUNCDETAIL_NORMAL,			/* found a matching regular function */
  	FUNCDETAIL_AGGREGATE,		/* found a matching aggregate function */
  	FUNCDETAIL_WINDOWFUNC,		/* found a matching window function */
! 	FUNCDETAIL_COERCION			/* it's a type coercion request */
  } FuncDetailCode;
  
- 
  extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
! 				  List *agg_order, Expr *agg_filter,
! 				  bool agg_star, bool agg_distinct, bool func_variadic,
! 				  WindowDef *over, bool is_column, int location);
  
  extern FuncDetailCode func_get_detail(List *funcname,
  				List *fargs, List *fargnames,
--- 38,48 ----
  	FUNCDETAIL_NORMAL,			/* found a matching regular function */
  	FUNCDETAIL_AGGREGATE,		/* found a matching aggregate function */
  	FUNCDETAIL_WINDOWFUNC,		/* found a matching window function */
! 	FUNCDETAIL_COERCION,			/* it's a type coercion request */
  } FuncDetailCode;
  
  extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
! 							   int location, FuncCall *fn);
  
  extern FuncDetailCode func_get_detail(List *funcname,
  				List *fargs, List *fargnames,
***************
*** 66,73 **** extern FuncCandidateList func_select_candidate(int nargs,
  
  extern void make_fn_arguments(ParseState *pstate,
  				  List *fargs,
  				  Oid *actual_arg_types,
! 				  Oid *declared_arg_types);
  
  extern const char *funcname_signature_string(const char *funcname, int nargs,
  						  List *argnames, const Oid *argtypes);
--- 63,72 ----
  
  extern void make_fn_arguments(ParseState *pstate,
  				  List *fargs,
+ 				  List *agg_order,
  				  Oid *actual_arg_types,
! 				  Oid *declared_arg_types,
! 				  bool requiresUnification);
  
  extern const char *funcname_signature_string(const char *funcname, int nargs,
  						  List *argnames, const Oid *argtypes);
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 665,670 **** extern Datum pg_get_functiondef(PG_FUNCTION_ARGS);
--- 665,672 ----
  extern Datum pg_get_function_arguments(PG_FUNCTION_ARGS);
  extern Datum pg_get_function_identity_arguments(PG_FUNCTION_ARGS);
  extern Datum pg_get_function_result(PG_FUNCTION_ARGS);
+ extern Datum pg_get_aggregate_arguments(PG_FUNCTION_ARGS);
+ extern Datum pg_get_aggregate_identity_arguments(PG_FUNCTION_ARGS);
  extern char *deparse_expression(Node *expr, List *dpcontext,
  				   bool forceprefix, bool showimplicit);
  extern List *deparse_context_for(const char *aliasname, Oid relid);
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
***************
*** 1310,1315 **** select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
--- 1310,1529 ----
   {"(2,2,bar)","(3,1,baz)"}
  (1 row)
  
+ -- ordered set functions
+ select p, percentile_cont(p) within group (order by x::float8) from generate_series(1,5) x, 
+ 	(values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) 
+ 	group by p order by p;
+   p   | percentile_cont 
+ ------+-----------------
+     0 |               1
+   0.1 |             1.4
+  0.25 |               2
+   0.4 |             2.6
+   0.5 |               3
+   0.6 |             3.4
+  0.75 |               4
+   0.9 |             4.6
+     1 |               5
+ (9 rows)
+ 
+ select p, percentile_cont(p order by p) within group (order by x::float8) 
+ 	from generate_series(1,5) x, (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) 
+ 	group by p order by x;
+ ERROR:  cannot have multiple ORDER BY clauses with WITHIN GROUP
+ LINE 1: select p, percentile_cont(p order by p) within group (order ...
+                                                 ^
+ select p, sum() within group (order by x::float8) 
+ 	from generate_series(1,5) x, 
+ 	(values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p;
+ ERROR:  sum(double precision) is not an ordered set function
+ select p, percentile_cont(p,p) from generate_series(1,5) x, 
+ 	(values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p;
+ ERROR:  WITHIN GROUP is required for call to ordered set function percentile_cont
+ LINE 1: select p, percentile_cont(p,p) from generate_series(1,5) x, 
+                   ^
+ select percentile_cont(0.5) within group (order by b) from aggtest;
+  percentile_cont  
+ ------------------
+  53.4485001564026
+ (1 row)
+ 
+ select percentile_cont(0.5) within group (order by b),sum(b) from aggtest;
+  percentile_cont  |   sum   
+ ------------------+---------
+  53.4485001564026 | 431.773
+ (1 row)
+ 
+ select percentile_cont(0.5) within group (order by thousand) from tenk1;
+  percentile_cont 
+ -----------------
+            499.5
+ (1 row)
+ 
+ select percentile_disc(0.5) within group (order by thousand) from tenk1;
+  percentile_disc 
+ -----------------
+              499
+ (1 row)
+ 
+ select rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+  rank 
+ ------
+     5
+ (1 row)
+ 
+ select cume_dist(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+  cume_dist 
+ -----------
+      0.875
+ (1 row)
+ 
+ select percent_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4),(5)) v(x);
+  percent_rank 
+ --------------
+           0.5
+ (1 row)
+ 
+ select dense_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+  dense_rank 
+ ------------
+           3
+ (1 row)
+ 
+ select percentile_disc(array[0,0.1,0.25,0.5,0.75,0.9,1]) within group (order by thousand) from tenk1;
+       percentile_disc       
+ ----------------------------
+  {0,99,249,499,749,899,999}
+ (1 row)
+ 
+ select percentile_cont(array[0,0.25,0.5,0.75,1]) within group (order by thousand) from tenk1;
+        percentile_cont       
+ -----------------------------
+  {0,249.75,499.5,749.25,999}
+ (1 row)
+ 
+ select percentile_disc(array[[null,1,0.5],[0.75,0.25,null]]) within group (order by thousand) from tenk1;
+          percentile_disc         
+ ---------------------------------
+  {{NULL,999,499},{749,249,NULL}}
+ (1 row)
+ 
+ select percentile_cont(array[0,1,0.25,0.75,0.5,1]) within group (order by x)
+   from generate_series(1,6) x;
+     percentile_cont    
+ -----------------------
+  {1,6,2.25,4.75,3.5,5}
+ (1 row)
+ 
+ select ten, mode() within group (order by string4) from tenk1 group by ten;
+  ten |  mode  
+ -----+--------
+    0 | HHHHxx
+    1 | OOOOxx
+    2 | VVVVxx
+    3 | OOOOxx
+    4 | HHHHxx
+    5 | HHHHxx
+    6 | OOOOxx
+    7 | AAAAxx
+    8 | VVVVxx
+    9 | VVVVxx
+ (10 rows)
+ 
+ select percentile_disc(array[0.25,0.5,0.75]) within group (order by x)
+   from unnest('{fred,jim,fred,jack,jill,fred,jill,jim,jim,sheila,jim,sheila}'::text[]) u(x);
+  percentile_disc 
+ -----------------
+  {fred,jill,jim}
+ (1 row)
+ 
+ -- ordered set funcs can't use ungrouped direct args:
+ select rank(x) within group (order by x) from generate_series(1,5) x;
+ ERROR:  column "x.x" must appear in the GROUP BY clause or be used in an aggregate function
+ LINE 1: select rank(x) within group (order by x) from generate_serie...
+                     ^
+ -- collation:
+ select pg_collation_for(percentile_disc(1) within group (order by x collate "POSIX"))
+   from (values ('fred'),('jim')) v(x);
+  pg_collation_for 
+ ------------------
+  "POSIX"
+ (1 row)
+ 
+ -- hypothetical type unification and argument failures:
+ select rank(3) within group (order by x) from (values ('fred'),('jim')) v(x);
+ ERROR:  WITHIN GROUP types text and integer cannot be matched
+ LINE 1: select rank(3) within group (order by x) from (values ('fred...
+                     ^
+ select rank(3) within group (order by stringu1,stringu2) from tenk1;
+ ERROR:  function rank has 2 ordering columns but 1 hypothetical arguments
+ LINE 1: select rank(3) within group (order by stringu1,stringu2) fro...
+                ^
+ select rank('fred') within group (order by x) from generate_series(1,5) x;
+ ERROR:  invalid input syntax for integer: "fred"
+ LINE 1: select rank('fred') within group (order by x) from generate_...
+                     ^
+ select rank('adam'::text collate "C") within group (order by x collate "POSIX")
+   from (values ('fred'),('jim')) v(x);
+ ERROR:  collation mismatch between explicit collations "C" and "POSIX"
+ LINE 1: ...adam'::text collate "C") within group (order by x collate "P...
+                                                              ^
+ -- hypothetical type unification successes:
+ select rank('adam'::varchar) within group (order by x) from (values ('fred'),('jim')) v(x);
+  rank 
+ ------
+     1
+ (1 row)
+ 
+ select rank('3') within group (order by x) from generate_series(1,5) x;
+  rank 
+ ------
+     3
+ (1 row)
+ 
+ -- divide by zero check
+ select percent_rank(0) within group (order by x) from generate_series(1,0) x;
+  percent_rank 
+ --------------
+             0
+ (1 row)
+ 
+ -- deparse and multiple features:
+ create view aggordview1 as
+ select ten,
+        percentile_disc(0.5) within group (order by thousand) as p50,
+        percentile_disc(0.5) within group (order by thousand) filter (where hundred=1) as px,
+        rank(5,'AZZZZ',50) within group (order by hundred, string4 desc, hundred)
+   from tenk1
+  group by ten order by ten;
+ select pg_get_viewdef('aggordview1');
+                                                         pg_get_viewdef                                                         
+ -------------------------------------------------------------------------------------------------------------------------------
+   SELECT tenk1.ten,                                                                                                           +
+      percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
+      percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
+      rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
+     FROM tenk1                                                                                                                +
+    GROUP BY tenk1.ten                                                                                                         +
+    ORDER BY tenk1.ten;
+ (1 row)
+ 
+ select * from aggordview1 order by ten;
+  ten | p50 | px  | rank 
+ -----+-----+-----+------
+    0 | 490 |     |  101
+    1 | 491 | 401 |  101
+    2 | 492 |     |  101
+    3 | 493 |     |  101
+    4 | 494 |     |  101
+    5 | 495 |     |   67
+    6 | 496 |     |    1
+    7 | 497 |     |    1
+    8 | 498 |     |    1
+    9 | 499 |     |    1
+ (10 rows)
+ 
+ drop view aggordview1;
  -- variadic aggregates
  select least_agg(q1,q2) from int8_tbl;
       least_agg     
*** a/src/test/regress/expected/opr_sanity.out
--- b/src/test/regress/expected/opr_sanity.out
***************
*** 700,708 **** SELECT * FROM funcdescs
  
  -- **************** pg_aggregate ****************
  -- Look for illegal values in pg_aggregate fields.
  SELECT ctid, aggfnoid::oid
  FROM pg_aggregate as p1
! WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0 OR aggtransspace < 0;
   ctid | aggfnoid 
  ------+----------
  (0 rows)
--- 700,717 ----
  
  -- **************** pg_aggregate ****************
  -- Look for illegal values in pg_aggregate fields.
+ -- ordered set functions can't have transfns, and must
+ -- have finalfns, but may or may not have transtypes.
+ -- other aggs must have transfns and transtypes with
+ -- optional finalfns.
  SELECT ctid, aggfnoid::oid
  FROM pg_aggregate as p1
! WHERE aggfnoid = 0
!    OR CASE WHEN aggisordsetfunc
!            THEN aggtransfn <> 0 OR aggfinalfn = 0
!            ELSE aggtransfn = 0 OR aggtranstype = 0
!       END
!    OR aggtransspace < 0;
   ctid | aggfnoid 
  ------+----------
  (0 rows)
***************
*** 764,771 **** WHERE a.aggfnoid = p.oid AND
      a.aggfinalfn = pfn.oid AND
      (pfn.proretset
       OR NOT binary_coercible(pfn.prorettype, p.prorettype)
!      OR pfn.pronargs != 1
!      OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]));
   aggfnoid | proname | oid | proname 
  ----------+---------+-----+---------
  (0 rows)
--- 773,781 ----
      a.aggfinalfn = pfn.oid AND
      (pfn.proretset
       OR NOT binary_coercible(pfn.prorettype, p.prorettype)
!      OR (aggisordsetfunc IS FALSE
!          AND (pfn.pronargs != 1
!               OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]))));
   aggfnoid | proname | oid | proname 
  ----------+---------+-----+---------
  (0 rows)
***************
*** 857,866 **** ORDER BY 1;
   count("any") | count()
  (1 row)
  
! -- For the same reason, we avoid creating built-in variadic aggregates.
! SELECT oid, proname
! FROM pg_proc AS p
! WHERE proisagg AND provariadic != 0;
   oid | proname 
  -----+---------
  (0 rows)
--- 867,878 ----
   count("any") | count()
  (1 row)
  
! -- For the same reason, we avoid creating built-in variadic aggregates, except
! -- ordered set functions (which have their own syntax and are not subject to
! -- the misplaced ORDER BY issue).
! SELECT p.oid, proname
! FROM pg_proc AS p JOIN pg_aggregate AS a ON (a.aggfnoid=p.oid)
! WHERE proisagg AND provariadic != 0 AND NOT a.aggisordsetfunc;
   oid | proname 
  -----+---------
  (0 rows)
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
***************
*** 493,498 **** select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
--- 493,564 ----
      from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
      generate_series(1,2) i;
  
+ -- ordered set functions
+ 
+ select p, percentile_cont(p) within group (order by x::float8) from generate_series(1,5) x, 
+ 	(values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) 
+ 	group by p order by p;
+ select p, percentile_cont(p order by p) within group (order by x::float8) 
+ 	from generate_series(1,5) x, (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) 
+ 	group by p order by x;
+ select p, sum() within group (order by x::float8) 
+ 	from generate_series(1,5) x, 
+ 	(values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p;
+ select p, percentile_cont(p,p) from generate_series(1,5) x, 
+ 	(values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p;
+ select percentile_cont(0.5) within group (order by b) from aggtest;
+ select percentile_cont(0.5) within group (order by b),sum(b) from aggtest;
+ select percentile_cont(0.5) within group (order by thousand) from tenk1;
+ select percentile_disc(0.5) within group (order by thousand) from tenk1;
+ select rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+ select cume_dist(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+ select percent_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4),(5)) v(x);
+ select dense_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+ 
+ select percentile_disc(array[0,0.1,0.25,0.5,0.75,0.9,1]) within group (order by thousand) from tenk1;
+ select percentile_cont(array[0,0.25,0.5,0.75,1]) within group (order by thousand) from tenk1;
+ select percentile_disc(array[[null,1,0.5],[0.75,0.25,null]]) within group (order by thousand) from tenk1;
+ select percentile_cont(array[0,1,0.25,0.75,0.5,1]) within group (order by x)
+   from generate_series(1,6) x;
+ 
+ select ten, mode() within group (order by string4) from tenk1 group by ten;
+ 
+ select percentile_disc(array[0.25,0.5,0.75]) within group (order by x)
+   from unnest('{fred,jim,fred,jack,jill,fred,jill,jim,jim,sheila,jim,sheila}'::text[]) u(x);
+ 
+ -- ordered set funcs can't use ungrouped direct args:
+ select rank(x) within group (order by x) from generate_series(1,5) x;
+ 
+ -- collation:
+ select pg_collation_for(percentile_disc(1) within group (order by x collate "POSIX"))
+   from (values ('fred'),('jim')) v(x);
+ 
+ -- hypothetical type unification and argument failures:
+ select rank(3) within group (order by x) from (values ('fred'),('jim')) v(x);
+ select rank(3) within group (order by stringu1,stringu2) from tenk1;
+ select rank('fred') within group (order by x) from generate_series(1,5) x;
+ select rank('adam'::text collate "C") within group (order by x collate "POSIX")
+   from (values ('fred'),('jim')) v(x);
+ -- hypothetical type unification successes:
+ select rank('adam'::varchar) within group (order by x) from (values ('fred'),('jim')) v(x);
+ select rank('3') within group (order by x) from generate_series(1,5) x;
+ 
+ -- divide by zero check
+ select percent_rank(0) within group (order by x) from generate_series(1,0) x;
+ 
+ -- deparse and multiple features:
+ create view aggordview1 as
+ select ten,
+        percentile_disc(0.5) within group (order by thousand) as p50,
+        percentile_disc(0.5) within group (order by thousand) filter (where hundred=1) as px,
+        rank(5,'AZZZZ',50) within group (order by hundred, string4 desc, hundred)
+   from tenk1
+  group by ten order by ten;
+ 
+ select pg_get_viewdef('aggordview1');
+ select * from aggordview1 order by ten;
+ drop view aggordview1;
+ 
  -- variadic aggregates
  select least_agg(q1,q2) from int8_tbl;
  select least_agg(variadic array[q1,q2]) from int8_tbl;
*** a/src/test/regress/sql/opr_sanity.sql
--- b/src/test/regress/sql/opr_sanity.sql
***************
*** 564,573 **** SELECT * FROM funcdescs
  -- **************** pg_aggregate ****************
  
  -- Look for illegal values in pg_aggregate fields.
  
  SELECT ctid, aggfnoid::oid
  FROM pg_aggregate as p1
! WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0 OR aggtransspace < 0;
  
  -- Make sure the matching pg_proc entry is sensible, too.
  
--- 564,582 ----
  -- **************** pg_aggregate ****************
  
  -- Look for illegal values in pg_aggregate fields.
+ -- ordered set functions can't have transfns, and must
+ -- have finalfns, but may or may not have transtypes.
+ -- other aggs must have transfns and transtypes with
+ -- optional finalfns.
  
  SELECT ctid, aggfnoid::oid
  FROM pg_aggregate as p1
! WHERE aggfnoid = 0
!    OR CASE WHEN aggisordsetfunc
!            THEN aggtransfn <> 0 OR aggfinalfn = 0
!            ELSE aggtransfn = 0 OR aggtranstype = 0
!       END
!    OR aggtransspace < 0;
  
  -- Make sure the matching pg_proc entry is sensible, too.
  
***************
*** 618,625 **** WHERE a.aggfnoid = p.oid AND
      a.aggfinalfn = pfn.oid AND
      (pfn.proretset
       OR NOT binary_coercible(pfn.prorettype, p.prorettype)
!      OR pfn.pronargs != 1
!      OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]));
  
  -- If transfn is strict then either initval should be non-NULL, or
  -- input type should match transtype so that the first non-null input
--- 627,635 ----
      a.aggfinalfn = pfn.oid AND
      (pfn.proretset
       OR NOT binary_coercible(pfn.prorettype, p.prorettype)
!      OR (aggisordsetfunc IS FALSE
!          AND (pfn.pronargs != 1
!               OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]))));
  
  -- If transfn is strict then either initval should be non-NULL, or
  -- input type should match transtype so that the first non-null input
***************
*** 685,695 **** WHERE p1.oid < p2.oid AND p1.proname = p2.proname AND
      array_dims(p1.proargtypes) != array_dims(p2.proargtypes)
  ORDER BY 1;
  
! -- For the same reason, we avoid creating built-in variadic aggregates.
  
! SELECT oid, proname
! FROM pg_proc AS p
! WHERE proisagg AND provariadic != 0;
  
  -- For the same reason, built-in aggregates with default arguments are no good.
  
--- 695,707 ----
      array_dims(p1.proargtypes) != array_dims(p2.proargtypes)
  ORDER BY 1;
  
! -- For the same reason, we avoid creating built-in variadic aggregates, except
! -- ordered set functions (which have their own syntax and are not subject to
! -- the misplaced ORDER BY issue).
  
! SELECT p.oid, proname
! FROM pg_proc AS p JOIN pg_aggregate AS a ON (a.aggfnoid=p.oid)
! WHERE proisagg AND provariadic != 0 AND NOT a.aggisordsetfunc;
  
  -- For the same reason, built-in aggregates with default arguments are no good.
  
