diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9af4697..083c75e 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -352,7 +352,7 @@
       <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>
+      <entry>Transition function (zero if none)</entry>
      </row>
      <row>
       <entry><structfield>aggfinalfn</structfield></entry>
@@ -370,7 +370,25 @@
       <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>
+      <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>agginitval</structfield></entry>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index a1d3aee..9ed7c5c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -12252,6 +12252,257 @@ SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab;
 
  </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>
+       <indexterm>
+        <primary>percentile</primary>
+        <secondary>discrete</secondary>
+       </indexterm>
+       <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>
+       <indexterm>
+        <primary>percentile</primary>
+        <secondary>continuous</secondary>
+       </indexterm>
+       <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 result)
+      </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="3">
+    <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>
 
diff --git a/doc/src/sgml/ref/alter_aggregate.sgml b/doc/src/sgml/ref/alter_aggregate.sgml
index aab5b2b..4fb3b6f 100644
--- a/doc/src/sgml/ref/alter_aggregate.sgml
+++ b/doc/src/sgml/ref/alter_aggregate.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
+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,10 +153,11 @@ 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>:
+   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 myavg(integer) SET SCHEMA myschema;
+ALTER AGGREGATE mypercentile(float8) WITHIN GROUP (integer) SET SCHEMA myschema;
 </programlisting></para>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml
index a14fcb4..5d326ae 100644
--- a/doc/src/sgml/ref/alter_extension.sgml
+++ b/doc/src/sgml/ref/alter_extension.sgml
@@ -30,7 +30,7 @@ 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> [, ...] ) |
+  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> |
diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml
index e550500..c62fa44 100644
--- a/doc/src/sgml/ref/comment.sgml
+++ b/doc/src/sgml/ref/comment.sgml
@@ -23,7 +23,7 @@ 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> [, ...] ) |
+  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> |
diff --git a/doc/src/sgml/ref/create_aggregate.sgml b/doc/src/sgml/ref/create_aggregate.sgml
index 2b35fa4..45b67c3 100644
--- a/doc/src/sgml/ref/create_aggregate.sgml
+++ b/doc/src/sgml/ref/create_aggregate.sgml
@@ -29,6 +29,15 @@ CREATE AGGREGATE <replaceable class="parameter">name</replaceable> ( [ <replacea
     [ , 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> (
@@ -70,7 +79,7 @@ CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
   </para>
 
   <para>
-   An aggregate function is made from one or two ordinary
+   An ordinary aggregate function is made from one or two ordinary
    functions:
    a state transition function
    <replaceable class="PARAMETER">sfunc</replaceable>,
@@ -165,6 +174,14 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
   </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
@@ -278,6 +295,11 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
       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>
 
@@ -305,6 +327,39 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
      </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>
diff --git a/doc/src/sgml/ref/drop_aggregate.sgml b/doc/src/sgml/ref/drop_aggregate.sgml
index 06060fb..2c5ab01 100644
--- a/doc/src/sgml/ref/drop_aggregate.sgml
+++ b/doc/src/sgml/ref/drop_aggregate.sgml
@@ -21,9 +21,13 @@ 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> [ , ... ] )
+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>
 
diff --git a/doc/src/sgml/ref/security_label.sgml b/doc/src/sgml/ref/security_label.sgml
index 76c131f..ebca07b 100644
--- a/doc/src/sgml/ref/security_label.sgml
+++ b/doc/src/sgml/ref/security_label.sgml
@@ -25,7 +25,7 @@ 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> [, ...] ) |
+  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> |
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index e3dbc4b..c3d26e8 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -1706,6 +1706,54 @@ SELECT string_agg(a ORDER BY a, ',') FROM table;  -- incorrect
    </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>
 
diff --git a/doc/src/sgml/xaggr.sgml b/doc/src/sgml/xaggr.sgml
index 9ed7d99..fc51602 100644
--- a/doc/src/sgml/xaggr.sgml
+++ b/doc/src/sgml/xaggr.sgml
@@ -9,20 +9,17 @@
   </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.
+   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>
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index d9e961e..bb44113 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -46,6 +46,7 @@ Oid
 AggregateCreate(const char *aggName,
 				Oid aggNamespace,
 				int numArgs,
+				int numDirectArgs,
 				oidvector *parameterTypes,
 				Datum allParameterTypes,
 				Datum parameterModes,
@@ -54,24 +55,29 @@ AggregateCreate(const char *aggName,
 				List *aggtransfnName,
 				List *aggfinalfnName,
 				List *aggsortopName,
+				List *aggtranssortopName,
 				Oid aggTransType,
-				const char *agginitval)
+				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;
+	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;
-	int			nargs_transfn;
+	Oid		   *fnArgs = palloc((numArgs + 1) * sizeof(Oid));
 	Oid			procOid;
 	TupleDesc	tupDesc;
 	int			i;
@@ -83,8 +89,20 @@ AggregateCreate(const char *aggName,
 	if (!aggName)
 		elog(ERROR, "no aggregate name supplied");
 
-	if (!aggtransfnName)
-		elog(ERROR, "aggregate must have a transition function");
+	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;
@@ -97,6 +115,136 @@ AggregateCreate(const char *aggName,
 			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.
@@ -107,53 +255,86 @@ 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);
+	if (!isOrderedSet)
+	{
+		/* find the transfn */		
 
-	/*
-	 * 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))));
+		fnArgs[0] = aggTransType;
+		memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
 
-	tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn));
-	if (!HeapTupleIsValid(tup))
-		elog(ERROR, "cache lookup failed for function %u", transfn);
-	proc = (Form_pg_proc) GETSTRUCT(tup);
+		transfn = lookup_agg_function(aggtransfnName, numArgs + 1, fnArgs,
+									  &rettype);
 
-	/*
-	 * 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))
+		/*
+		 * 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_INVALID_FUNCTION_DEFINITION),
-					 errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
+					(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);
 	}
-	ReleaseSysCache(tup);
 
 	/* handle finalfn, if supplied */
-	if (aggfinalfnName)
+	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,
@@ -166,6 +347,7 @@ AggregateCreate(const char *aggName,
 		 */
 		finaltype = aggTransType;
 	}
+
 	Assert(OidIsValid(finaltype));
 
 	/*
@@ -207,6 +389,18 @@ AggregateCreate(const char *aggName,
 								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
 	 */
@@ -217,15 +411,17 @@ 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);
+	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.)
@@ -246,7 +442,7 @@ AggregateCreate(const char *aggName,
 							  false,	/* security invoker (currently not
 										 * definable for agg) */
 							  false,	/* isLeakProof */
-							  false,	/* isStrict (not needed for agg) */
+							  isStrict,	/* isStrict (needed for ordered set funcs) */
 							  PROVOLATILE_IMMUTABLE,	/* volatility (not
 														 * needed for agg) */
 							  parameterTypes,	/* paramTypes */
@@ -272,7 +468,11 @@ AggregateCreate(const char *aggName,
 	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);
+
 	if (agginitval)
 		values[Anum_pg_aggregate_agginitval - 1] = CStringGetTextDatum(agginitval);
 	else
@@ -290,18 +490,23 @@ 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.
+	 * 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 */
-	referenced.classId = ProcedureRelationId;
-	referenced.objectId = transfn;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	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))
@@ -321,6 +526,24 @@ AggregateCreate(const char *aggName,
 		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;
 }
 
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 78af092..9774667 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -44,9 +44,12 @@
  *	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.
+ * 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,18 +61,23 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
 	List	   *transfuncName = NIL;
 	List	   *finalfuncName = NIL;
 	List	   *sortoperatorName = NIL;
+	List	   *transsortoperatorName = NIL;
 	TypeName   *baseType = NULL;
 	TypeName   *transType = NULL;
 	char	   *initval = NULL;
 	int			numArgs;
+	int			numDirectArgs = -1;
+	Oid			transTypeId = InvalidOid;
 	oidvector  *parameterTypes;
 	ArrayType  *allParameterTypes;
 	ArrayType  *parameterModes;
 	ArrayType  *parameterNames;
 	List	   *parameterDefaults;
-	Oid			transTypeId;
 	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);
@@ -80,6 +88,14 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
 		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);
@@ -106,6 +122,12 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
 			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),
@@ -113,17 +135,35 @@ 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")));
+	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).
@@ -173,8 +213,15 @@ 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,
+		/*
+		 * 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,
@@ -191,7 +238,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
 	}
 
 	/*
-	 * look up the aggregate's transtype.
+	 * 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
@@ -201,18 +248,20 @@ 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 (transType)
 	{
-		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))));
+		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))));
+		}
+
 	}
 
 	/*
@@ -224,13 +273,23 @@ 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)
+	if (transType)
 	{
-		Oid			typinput,
-					typioparam;
+		if (initval && transTypeType != TYPTYPE_PSEUDO)
+		{
+			Oid			typinput,
+						typioparam;
 
-		getTypeInputInfo(transTypeId, &typinput, &typioparam);
-		(void) OidInputFunctionCall(typinput, initval, typioparam, -1);
+			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")));
 	}
 
 	/*
@@ -239,6 +298,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
 	return AggregateCreate(aggName,		/* aggregate name */
 						   aggNamespace,		/* namespace */
 						   numArgs,
+						   numDirectArgs,
 						   parameterTypes,
 						   PointerGetDatum(allParameterTypes),
 						   PointerGetDatum(parameterModes),
@@ -247,6 +307,10 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
 						   transfuncName,		/* step function name */
 						   finalfuncName,		/* final function name */
 						   sortoperatorName,	/* sort operator name */
+						   transsortoperatorName,  /* transsort operator name */
 						   transTypeId, /* transition data type */
-						   initval);	/* initial condition */
+						   initval,  /* initial condition */
+						   isStrict,  /* is explicitly STRICT */
+						   isOrderedSet,  /* If the function is an ordered set */
+						   ishypothetical);  /* If the function is a hypothetical set */
 }
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index ca754b4..2399446 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -274,8 +274,13 @@ 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)
+			/*
+			 * 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")));
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 90c2753..e6fb8b0 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -4410,6 +4410,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
 
 					astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
 														 parent);
+					astate->orddirectargs = (List *) ExecInitExpr((Expr *) aggref->orddirectargs, parent);
 					astate->aggfilter = ExecInitExpr(aggref->aggfilter,
 													 parent);
 
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index ff6a123..f216cd4 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -380,8 +380,8 @@ 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);
+								  cref->location,
+								  NULL);
 	}
 
 	return param;
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index e02a6ff..3f2cf8d 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -3,7 +3,7 @@
  * nodeAgg.c
  *	  Routines to handle aggregate nodes.
  *
- *	  ExecAgg evaluates each aggregate in the following steps:
+ *	  ExecAgg evaluates each normal aggregate in the following steps:
  *
  *		 transvalue = initcond
  *		 foreach input_tuple do
@@ -66,6 +66,26 @@
  *	  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,10 +107,12 @@
 #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,6 +127,8 @@
  */
 typedef struct AggStatePerAggData
 {
+	NodeTag type;
+	
 	/*
 	 * These values are set up during ExecInitAgg() and do not change
 	 * thereafter:
@@ -114,10 +138,25 @@ typedef struct AggStatePerAggData
 	AggrefExprState *aggrefstate;
 	Aggref	   *aggref;
 
-	/* number of input arguments for aggregate function proper */
+	/* 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 */
+	/*
+	 * 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,12 +165,23 @@ 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.
+	 * 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,6 +198,9 @@ typedef struct AggStatePerAggData
 	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,6 +257,9 @@ typedef struct AggStatePerAggData
 	 */
 
 	Tuplesortstate *sortstate;	/* sort object, if DISTINCT or ORDER BY */
+
+	int64 number_of_rows;		/* number of rows */
+
 }	AggStatePerAggData;
 
 /*
@@ -300,6 +356,8 @@ initialize_aggregates(AggState *aggstate,
 		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,14 +441,17 @@ advance_transition_function(AggState *aggstate,
 	MemoryContext oldContext;
 	Datum		newVal;
 	int			i;
+	int         numStrict = peraggstate->numStrict;
 
-	if (peraggstate->transfn.fn_strict)
+	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 <= numArguments; i++)
+		for (i = 1; i <= numStrict; i++)
 		{
 			if (fcinfo->argnull[i])
 				return;
@@ -506,24 +567,24 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
 
 		if (peraggstate->numSortCols > 0)
 		{
+			int numStrict = peraggstate->numStrict;
+
 			/* DISTINCT and/or ORDER BY case */
 			Assert(slot->tts_nvalid == peraggstate->numInputs);
 
 			/*
-			 * If the transfn is strict, we want to check for nullity before
+			 * 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.  Note that we must only check numArguments columns,
-			 * not numInputs, since nullity in columns used only for sorting
-			 * is not relevant here.
+			 * of nulls.
 			 */
-			if (peraggstate->transfn.fn_strict)
+			if (numStrict > 0)
 			{
-				for (i = 0; i < nargs; i++)
+				for (i = 0; i < numStrict; i++)
 				{
 					if (slot->tts_isnull[i])
 						break;
 				}
-				if (i < nargs)
+				if (i < numStrict)
 					continue;
 			}
 
@@ -534,6 +595,8 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
 								   slot->tts_isnull[0]);
 			else
 				tuplesort_puttupleslot(peraggstate->sortstate, slot);
+
+			peraggstate->number_of_rows++;
 		}
 		else
 		{
@@ -756,15 +819,66 @@ finalize_aggregate(AggState *aggstate,
 	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;
 
-		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)
+				++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 */
+			/*
+			 * 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,6 +1278,17 @@ 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.)
+		 * 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,14 +1299,17 @@ 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);
+				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,15 +1317,6 @@ 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.
 		 */
@@ -1568,10 +1687,12 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 		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,6 +1701,10 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 		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,31 +1726,18 @@ 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->type = T_AggStatePerAggData;
+		peraggstate->aggstate = aggstate;
 		peraggstate->aggref = aggref;
-		numInputs = list_length(aggref->args);
-		peraggstate->numInputs = numInputs;
-		peraggstate->sortstate = NULL;
+		peraggstate->aggrefstate = aggrefstate;
 
-		/*
-		 * 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);
+		peraggstate->isOrderedSet = isOrderedSet;
 
-			if (!tle->resjunk)
-				inputTypes[numArguments++] = exprType((Node *) tle->expr);
-		}
-		peraggstate->numArguments = numArguments;
+		/* 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,6 +1745,13 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 				 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,25 +1763,37 @@ 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 */
+		/*
+		 * 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);
-			aggOwner = ((Form_pg_proc) GETSTRUCT(procTuple))->proowner;
+			procp = (Form_pg_proc) GETSTRUCT(procTuple);
+			aggOwner = procp->proowner;
+			is_strict = procp->proisstrict;
 			ReleaseSysCache(procTuple);
 
-			aclresult = pg_proc_aclcheck(transfn_oid, aggOwner,
-										 ACL_EXECUTE);
-			if (aclresult != ACLCHECK_OK)
+			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);
+				InvokeFunctionExecuteHook(transfn_oid);
+			}
+
 			if (OidIsValid(finalfn_oid))
 			{
 				aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
@@ -1674,17 +1805,37 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 			}
 		}
 
+		/*
+		 * 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 (IsPolymorphicType(aggtranstype))
+		if (OidIsValid(aggtranstype) && IsPolymorphicType(aggtranstype))
 		{
 			/* have to fetch the agg's declared input types... */
 			Oid		   *declaredArgTypes;
-			int			agg_nargs;
+			int         agg_nargs;
 
 			(void) get_func_signature(aggref->aggfnoid,
-									  &declaredArgTypes, &agg_nargs);
-			Assert(agg_nargs == numArguments);
+									  &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,35 +1844,82 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 			pfree(declaredArgTypes);
 		}
 
+		aggtranstypecoll = get_typcollation(aggtranstype);
+
 		/* 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 (!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);
-		get_typlenbyval(aggtranstype,
-						&peraggstate->transtypeLen,
-						&peraggstate->transtypeByVal);
+		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,7 +1942,9 @@ 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 (OidIsValid(peraggstate->transfn_oid)
+			&& peraggstate->transfn.fn_strict
+			&& peraggstate->initValueIsNull)
 		{
 			if (numArguments < 1 ||
 				!IsBinaryCoercible(inputTypes[0], aggtranstype))
@@ -1754,21 +1954,8 @@ 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);
+		argexprs = aggref->args;
+		argexprstate = aggrefstate->args;
 
 		/*
 		 * If we're doing either DISTINCT or ORDER BY, then we have a list of
@@ -1777,6 +1964,11 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 		 *
 		 * 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,11 +1980,86 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 		{
 			sortlist = aggref->aggorder;
 			numSortCols = list_length(sortlist);
-			numDistinctCols = 0;
+			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,11 +2072,12 @@ 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],
+				get_typlenbyval(peraggstate->evaldesc->attrs[0]->atttypid,
 								&peraggstate->inputtypeLen,
 								&peraggstate->inputtypeByVal);
 			}
-			else if (numDistinctCols > 0)
+
+			if (numDistinctCols > 0 && (numInputs > 1 || isOrderedSet))
 			{
 				/* we will need an extra slot to store prior values */
 				peraggstate->uniqslot = ExecInitExtraTupleSlot(estate);
@@ -1822,50 +2090,47 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 				(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,
-														   aggref->args);
+														   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;
-				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);
+				/*
+				 * 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]);
 
-				fmgr_info(get_opcode(sortcl->eqop), &peraggstate->equalfns[i]);
 				i++;
 			}
-			Assert(i == numDistinctCols);
+			Assert(i == numSortCols);
 		}
 
 		ReleaseSysCache(aggTuple);
@@ -2023,6 +2288,9 @@ ExecReScanAgg(AggState *node)
  * 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,3 +2331,118 @@ aggregate_dummy(PG_FUNCTION_ARGS)
 		 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;
+	}
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 65f3b98..12396a5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1139,9 +1139,12 @@ _copyAggref(const Aggref *from)
 	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,6 +2177,7 @@ _copyFuncCall(const FuncCall *from)
 	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);
 
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4c9b05e..537a5e4 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -196,9 +196,12 @@ _equalAggref(const Aggref *a, const Aggref *b)
 	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);
 
@@ -2006,6 +2009,7 @@ _equalFuncCall(const FuncCall *a, const FuncCall *b)
 	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);
 
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index b742ec9..5be82ba 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -558,6 +558,7 @@ makeFuncCall(List *name, List *args, int location)
 	n->agg_star = FALSE;
 	n->agg_distinct = FALSE;
 	n->func_variadic = FALSE;
+	n->has_within_group = FALSE;
 	n->over = NULL;
 	return n;
 }
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 908f397..e84b371 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1631,6 +1631,11 @@ expression_tree_walker(Node *node,
 				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,7 +2160,9 @@ expression_tree_mutator(Node *node,
 				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;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 817b149..8f6e293 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -960,9 +960,12 @@ _outAggref(StringInfo str, const Aggref *node)
 	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);
 }
@@ -2090,6 +2093,7 @@ _outFuncCall(StringInfo str, const FuncCall *node)
 	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);
 }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index d325bb3..950645d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -495,9 +495,12 @@ _readAggref(void)
 	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);
 
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 76c032c..6820e82 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -39,6 +39,7 @@
 #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"
@@ -464,7 +465,6 @@ count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
 		QualCost	argcosts;
 		Oid		   *inputTypes;
 		int			numArguments;
-		ListCell   *l;
 
 		Assert(aggref->agglevelsup == 0);
 
@@ -486,7 +486,8 @@ 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(aggtransfn))
+			costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost;
 		if (OidIsValid(aggfinalfn))
 			costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost;
 
@@ -504,72 +505,91 @@ 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)
+		/*
+		 * 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)
 		{
-			TargetEntry *tle = (TargetEntry *) lfirst(l);
+			Assert(!aggref->isordset);
 
-			if (!tle->resjunk)
-				inputTypes[numArguments++] = exprType((Node *) tle->expr);
-		}
+			/* extract argument types (ignoring any ORDER BY expressions) */
+			inputTypes = (Oid *) palloc(sizeof(Oid) * FUNC_MAX_ARGS);
 
-		/* 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);
-		}
+			numArguments = get_aggregate_argtypes(aggref, inputTypes, NULL);
 
-		/*
-		 * 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		aggtranstypmod;
-			int32		avgwidth;
+			/* 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 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.
+			 * 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 (numArguments > 0 && aggtranstype == inputTypes[0])
-				aggtranstypmod = exprTypmod((Node *) linitial(aggref->args));
-			else
-				aggtranstypmod = -1;
+			if (OidIsValid(aggtranstype) && !get_typbyval(aggtranstype))
+			{
+				int32		aggtranstypmod;
+				int32		avgwidth;
+
+				/*
+				 * 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.
+				 */
+				if (numArguments > 0 && aggtranstype == inputTypes[0])
+					aggtranstypmod = exprTypmod((Node *) linitial(aggref->args));
+				else
+					aggtranstypmod = -1;
 
-			avgwidth = get_typavgwidth(aggtranstype, aggtranstypmod);
-			avgwidth = MAXALIGN(avgwidth);
+				avgwidth = get_typavgwidth(aggtranstype, aggtranstypmod);
+				avgwidth = MAXALIGN(avgwidth);
 
-			costs->transitionSpace += avgwidth + 2 * sizeof(void *);
+				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.  We assume usage of
+				 * 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.
+				 */
+				costs->transitionSpace += ALLOCSET_DEFAULT_INITSIZE;
+			}
+
+			pfree(inputTypes);
 		}
-		else if (aggtranstype == INTERNALOID)
+		else
 		{
-			/*
-			 * 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.  We assume usage of
-			 * 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.
-			 */
-			costs->transitionSpace += ALLOCSET_DEFAULT_INITSIZE;
+			costs->transitionSpace = work_mem;   /* just in case */
 		}
 
 		/*
@@ -3826,7 +3846,7 @@ 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);
+	make_fn_arguments(NULL, args, NULL, actual_arg_types, declared_arg_types, false);
 }
 
 /*
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index a9d1fec..ae168ad 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -951,7 +951,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 										  &qry->targetList,
 										  EXPR_KIND_ORDER_BY,
 										  true /* fix unknowns */ ,
-										  false /* allow SQL92 rules */ );
+										  false /* allow SQL92 rules */,
+										  false /* don't add duplicates */);
 
 	qry->groupClause = transformGroupClause(pstate,
 											stmt->groupClause,
@@ -1211,7 +1212,8 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 										  &qry->targetList,
 										  EXPR_KIND_ORDER_BY,
 										  true /* fix unknowns */ ,
-										  false /* allow SQL92 rules */ );
+										  false /* allow SQL92 rules */,
+										  false /* don't add duplicates */ );
 
 	qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
 											EXPR_KIND_OFFSET, "OFFSET");
@@ -1435,7 +1437,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 										  &qry->targetList,
 										  EXPR_KIND_ORDER_BY,
 										  false /* no unknowns expected */ ,
-										  false /* allow SQL92 rules */ );
+										  false /* allow SQL92 rules */ ,
+										  false /* don't add duplicates */ );
 
 	/* restore namespace, remove jrte from rtable */
 	pstate->p_namespace = sv_namespace;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 363c603..869d876 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -494,6 +494,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %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.
@@ -596,7 +597,7 @@ 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
+	WHEN WHERE WITHIN WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
 	XMLPI XMLROOT XMLSERIALIZE
@@ -3664,7 +3665,7 @@ AlterExtensionContentsStmt:
 					n->action = $4;
 					n->objtype = OBJECT_AGGREGATE;
 					n->objname = $6;
-					n->objargs = extractArgTypes($7);
+					n->objargs = extractArgTypes(linitial($7));
 					$$ = (Node *)n;
 				}
 			| ALTER EXTENSION name add_drop CAST '(' Typename AS Typename ')'
@@ -5243,7 +5244,7 @@ CommentStmt:
 					CommentStmt *n = makeNode(CommentStmt);
 					n->objtype = OBJECT_AGGREGATE;
 					n->objname = $4;
-					n->objargs = extractArgTypes($5);
+					n->objargs = extractArgTypes(linitial($5));
 					n->comment = $7;
 					$$ = (Node *) n;
 				}
@@ -5409,7 +5410,7 @@ SecLabelStmt:
 					n->provider = $3;
 					n->objtype = OBJECT_AGGREGATE;
 					n->objname = $6;
-					n->objargs = extractArgTypes($7);
+					n->objargs = extractArgTypes(linitial($7));
 					n->label = $9;
 					$$ = (Node *) n;
 				}
@@ -6409,9 +6410,53 @@ aggr_arg:	func_arg
 				}
 		;
 
-/* Zero-argument aggregates are named with * for consistency with COUNT(*) */
-aggr_args:	'(' aggr_args_list ')'					{ $$ = $2; }
-			| '(' '*' ')'							{ $$ = NIL; }
+/*
+ * 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:
@@ -6617,7 +6662,7 @@ RemoveAggrStmt:
 					DropStmt *n = makeNode(DropStmt);
 					n->removeType = OBJECT_AGGREGATE;
 					n->objects = list_make1($3);
-					n->arguments = list_make1(extractArgTypes($4));
+					n->arguments = list_make1(extractArgTypes(linitial($4)));
 					n->behavior = $5;
 					n->missing_ok = false;
 					n->concurrent = false;
@@ -6628,7 +6673,7 @@ RemoveAggrStmt:
 					DropStmt *n = makeNode(DropStmt);
 					n->removeType = OBJECT_AGGREGATE;
 					n->objects = list_make1($5);
-					n->arguments = list_make1(extractArgTypes($6));
+					n->arguments = list_make1(extractArgTypes(linitial($6)));
 					n->behavior = $7;
 					n->missing_ok = true;
 					n->concurrent = false;
@@ -6844,7 +6889,7 @@ 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->objarg = extractArgTypes(linitial($4));
 					n->newname = $7;
 					n->missing_ok = false;
 					$$ = (Node *)n;
@@ -7318,7 +7363,7 @@ AlterObjectSchemaStmt:
 					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
 					n->objectType = OBJECT_AGGREGATE;
 					n->object = $3;
-					n->objarg = extractArgTypes($4);
+					n->objarg = extractArgTypes(linitial($4));
 					n->newschema = $7;
 					n->missing_ok = false;
 					$$ = (Node *)n;
@@ -7547,7 +7592,7 @@ 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->objarg = extractArgTypes(linitial($4));
 					n->newowner = $7;
 					$$ = (Node *)n;
 				}
@@ -9435,6 +9480,11 @@ sortby:		a_expr USING qual_all_Op opt_nulls_order
 		;
 
 
+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); }
@@ -11140,12 +11190,35 @@ 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 
+func_expr: func_application within_group_clause filter_clause over_clause 
 				{
-              		FuncCall *n = (FuncCall*)$1;
-					n->agg_filter = $2;
-					n->over = $3;
-					$$ = (Node*)n;
+              		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; }
@@ -12704,6 +12777,7 @@ unreserved_keyword:
 			| VIEW
 			| VOLATILE
 			| WHITESPACE_P
+			| WITHIN
 			| WITHOUT
 			| WORK
 			| WRAPPER
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 98cb58a..23954a0 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -44,7 +44,9 @@ typedef struct
 	int			sublevels_up;
 } check_ungrouped_columns_context;
 
-static int	check_agg_arguments(ParseState *pstate, List *args, Expr *filter);
+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,7 +77,8 @@ static bool check_ungrouped_columns_walker(Node *node,
  */
 void
 transformAggregateCall(ParseState *pstate, Aggref *agg,
-					   List *args, List *aggorder, bool agg_distinct)
+					   List *args, List *aggorder,
+					   bool agg_distinct, bool agg_within_group)
 {
 	List	   *tlist;
 	List	   *torder;
@@ -93,12 +96,24 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 	 */
 	tlist = NIL;
 	attno = 1;
-	foreach(lc, args)
+
+	if (agg_within_group)
 	{
-		Expr	   *arg = (Expr *) lfirst(lc);
-		TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false);
+		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);
+			tlist = lappend(tlist, tle);
+		}
+
+		agg->isordset = FALSE;
+		agg->orddirectargs = NIL;
 	}
 
 	/*
@@ -109,6 +124,11 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 	 *
 	 * 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,7 +138,8 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 								 &tlist,
 								 EXPR_KIND_ORDER_BY,
 								 true /* fix unknowns */ ,
-								 true /* force SQL99 rules */ );
+								 true /* force SQL99 rules */ ,
+								 agg_within_group /* keep duplicates? */ );
 
 	/*
 	 * If we have DISTINCT, transform that to produce a distinctList.
@@ -160,7 +181,8 @@ 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);
+	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,7 +334,7 @@ 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)
+check_agg_arguments(ParseState *pstate, List *args, List *agg_ordset, Expr *filter)
 {
 	int			agglevel;
 	check_agg_arguments_context context;
@@ -330,6 +352,9 @@ check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
 								  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,8 +378,8 @@ 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))));
+								locate_agg_of_level((Node *) args,
+								agglevel))));
 
 	return agglevel;
 }
@@ -823,8 +848,16 @@ 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)
+		(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,3 +1075,98 @@ build_aggregate_fnexprs(Oid *agg_input_types,
 										 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;
+}
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index ea90e58..88484b2 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -71,7 +71,8 @@ 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);
+											 List **tlist, ParseExprKind exprKind,
+											 bool keepDuplicates);
 static int get_matching_location(int sortgroupref,
 					  List *sortgrouprefs, List *exprs);
 static List *addTargetToSortList(ParseState *pstate, TargetEntry *tle,
@@ -1476,7 +1477,7 @@ findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
 	/*
 	 * Otherwise, we have an expression, so process it per SQL99 rules.
 	 */
-	return findTargetlistEntrySQL99(pstate, node, tlist, exprKind);
+	return findTargetlistEntrySQL99(pstate, node, tlist, exprKind, false);
 }
 
 /*
@@ -1491,10 +1492,11 @@ 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
+ * keepDuplicates  if true, don't try and match to any existing entry
  */
 static TargetEntry *
 findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
-						 ParseExprKind exprKind)
+						 ParseExprKind exprKind, bool keepDuplicates)
 {
 	TargetEntry *target_result;
 	ListCell   *tl;
@@ -1509,24 +1511,27 @@ findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
 	 */
 	expr = transformExpr(pstate, node, exprKind);
 
-	foreach(tl, *tlist)
+	if (!keepDuplicates)
 	{
-		TargetEntry *tle = (TargetEntry *) lfirst(tl);
-		Node	   *texpr;
+		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);
+			/*
+			 * 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;
+			if (equal(expr, texpr))
+				return tle;
+		}
 	}
 
 	/*
@@ -1568,7 +1573,7 @@ transformGroupClause(ParseState *pstate, List *grouplist,
 
 		if (useSQL99)
 			tle = findTargetlistEntrySQL99(pstate, gexpr,
-										   targetlist, exprKind);
+										   targetlist, exprKind, false);
 		else
 			tle = findTargetlistEntrySQL92(pstate, gexpr,
 										   targetlist, exprKind);
@@ -1635,11 +1640,14 @@ transformSortClause(ParseState *pstate,
 					List **targetlist,
 					ParseExprKind exprKind,
 					bool resolveUnknown,
-					bool useSQL99)
+					bool useSQL99,
+	                bool keepDuplicates)
 {
 	List	   *sortlist = NIL;
 	ListCell   *olitem;
 
+	Assert(useSQL99 || !keepDuplicates);
+
 	foreach(olitem, orderlist)
 	{
 		SortBy	   *sortby = (SortBy *) lfirst(olitem);
@@ -1647,7 +1655,7 @@ transformSortClause(ParseState *pstate,
 
 		if (useSQL99)
 			tle = findTargetlistEntrySQL99(pstate, sortby->node,
-										   targetlist, exprKind);
+										   targetlist, exprKind, keepDuplicates);
 		else
 			tle = findTargetlistEntrySQL92(pstate, sortby->node,
 										   targetlist, exprKind);
@@ -1717,7 +1725,8 @@ transformWindowDefinitions(ParseState *pstate,
 										  targetlist,
 										  EXPR_KIND_WINDOW_ORDER,
 										  true /* fix unknowns */ ,
-										  true /* force SQL99 rules */ );
+										  true /* force SQL99 rules */,
+			                              false /* don't add duplicates */);
 		partitionClause = transformGroupClause(pstate,
 											   windef->partitionClause,
 											   targetlist,
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index fe57c59..f3499fc 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -73,7 +73,9 @@ typedef struct
 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,44 +566,16 @@ 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 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;
-							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_aggregate_collations(aggref, context, &loccontext);
 
 							assign_expr_collations(context->pstate,
-												 (Node *) aggref->aggfilter);
+												   (Node *) aggref->aggfilter);
 						}
 						break;
 					case T_WindowFunc:
@@ -802,3 +776,159 @@ assign_collations_walker(Node *node, assign_collations_context *context)
 
 	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);
+		}
+	}
+}
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 68b711d..1800a68 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -463,8 +463,8 @@ transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
 										  list_make1(result),
-										  NIL, NULL, false, false, false,
-										  NULL, true, location);
+										  location,
+										  NULL);
 			if (newresult == NULL)
 				unknown_attribute(pstate, result, strVal(n), location);
 			result = newresult;
@@ -631,8 +631,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 					node = ParseFuncOrColumn(pstate,
 											 list_make1(makeString(colname)),
 											 list_make1(node),
-											 NIL, NULL, false, false, false,
-											 NULL, true, cref->location);
+											 cref->location, NULL);
 				}
 				break;
 			}
@@ -676,8 +675,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 					node = ParseFuncOrColumn(pstate,
 											 list_make1(makeString(colname)),
 											 list_make1(node),
-											 NIL, NULL, false, false, false,
-											 NULL, true, cref->location);
+											 cref->location, NULL);
 				}
 				break;
 			}
@@ -734,8 +732,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 					node = ParseFuncOrColumn(pstate,
 											 list_make1(makeString(colname)),
 											 list_make1(node),
-											 NIL, NULL, false, false, false,
-											 NULL, true, cref->location);
+											 cref->location, NULL);
 				}
 				break;
 			}
@@ -1242,38 +1239,20 @@ 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)));
+		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);
+							 fn->location,
+							 fn);
 }
 
 static Node *
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 2bd24c8..2ea5aa9 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -17,16 +17,19 @@
 #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,15 +59,21 @@ 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.
+ *	The argument expressions (in fargs) must have been transformed
+ *	already.
  */
 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)
+				  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,6 +90,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 	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,6 +113,15 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 				 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,6 +187,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 		}
 	}
 
+	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,6 +200,26 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 	}
 
 	/*
+	 * 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,6 +297,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 			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,6 +322,53 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 							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,13 +454,16 @@ 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);
+	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 && declared_arg_types[nargs - 1] != ANYOID)
+	if (nvargs > 0 && vatype != ANYOID)
 	{
 		ArrayExpr  *newa = makeNode(ArrayExpr);
 		int			non_var_args = nargs - nvargs;
@@ -388,16 +494,31 @@ 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.
+	 *
+	 * 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)
 	{
-		Oid		va_arr_typid = actual_arg_types[nargs - 1];
+		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 *) llast(fargs)))));
+			  parser_errposition(pstate,
+								 exprLocation((Node *) list_nth(fargs, nargs - 1 - ignore_args)))));
 	}
 
 	/* build the appropriate output structure */
@@ -421,6 +542,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 		/* 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,14 +555,24 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 		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)
+		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,7 +601,8 @@ 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);
+		transformAggregateCall(pstate, aggref, fargs, agg_order,
+							   agg_distinct, agg_within_group);
 
 		retval = (Node *) aggref;
 	}
@@ -473,6 +611,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 		/* 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.
 		 */
@@ -1374,11 +1518,21 @@ func_get_detail(List *funcname,
 void
 make_fn_arguments(ParseState *pstate,
 				  List *fargs,
+				  List *agg_order,
 				  Oid *actual_arg_types,
-				  Oid *declared_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)
 	{
@@ -1386,6 +1540,7 @@ make_fn_arguments(ParseState *pstate,
 		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
@@ -1406,18 +1561,66 @@ 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;
+				/* 
+				 * 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++;
+	}
 }
 
 /*
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index dd80fa9..08cbabd 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -823,7 +823,7 @@ 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);
+	make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types, false);
 
 	/* and build the expression node */
 	result = makeNode(OpExpr);
@@ -953,7 +953,7 @@ 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);
+	make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types, false);
 
 	/* and build the expression node */
 	result = makeNode(ScalarArrayOpExpr);
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 41a8982..8ab553e 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -19,13 +19,13 @@ 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 \
+	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 \
-	ri_triggers.o pg_lzcompress.o pg_locale.o formatting.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 \
diff --git a/src/backend/utils/adt/hypotheticalset.c b/src/backend/utils/adt/hypotheticalset.c
new file mode 100644
index 0000000..98af805
--- /dev/null
+++ b/src/backend/utils/adt/hypotheticalset.c
@@ -0,0 +1,219 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 = (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);
+}
diff --git a/src/backend/utils/adt/inversedistribution.c b/src/backend/utils/adt/inversedistribution.c
new file mode 100644
index 0000000..fdda722
--- /dev/null
+++ b/src/backend/utils/adt/inversedistribution.c
@@ -0,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);
+}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 04b1c4f..885d7a8 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -22,6 +22,7 @@
 #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,6 +294,9 @@ static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
 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,
@@ -402,6 +406,8 @@ static char *generate_function_name(Oid funcid, int nargs,
 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 ")
 
@@ -2267,6 +2273,149 @@ print_function_arguments(StringInfo buf, HeapTuple proctup,
 
 
 /*
+ * 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
@@ -7388,6 +7537,80 @@ static void
 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;
@@ -7442,14 +7665,6 @@ 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, ')');
 }
 
 /*
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index ea8af9f..7375d8e 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -1411,23 +1411,29 @@ 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.
+ * 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);
-			*should_free = false;
+			if (should_free)
+				*should_free = false;
 			if (forward)
 			{
 				if (state->current < state->memtupcount)
 				{
-					*stup = state->memtuples[state->current++];
+					if (stup)
+						*stup = state->memtuples[state->current];
+					state->current++;
 					return true;
 				}
 				state->eof_reached = true;
@@ -1459,21 +1465,25 @@ tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
 					if (state->current <= 0)
 						return false;
 				}
-				*stup = state->memtuples[state->current - 1];
+				if (stup)
+					*stup = state->memtuples[state->current - 1];
 				return true;
 			}
 			break;
 
 		case TSS_SORTEDONTAPE:
 			Assert(forward || state->randomAccess);
-			*should_free = true;
+			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, stup, state->result_tape, tuplen);
+					READTUP(state, ptup, state->result_tape, tuplen);
+					if (!stup && dummy.tuple)
+						pfree(dummy.tuple);
 					return true;
 				}
 				else
@@ -1546,12 +1556,15 @@ 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);
+			READTUP(state, ptup, state->result_tape, tuplen);
+			if (!stup && dummy.tuple)
+				pfree(dummy.tuple);
 			return true;
 
 		case TSS_FINALMERGE:
 			Assert(forward);
-			*should_free = true;
+			if (should_free)
+				*should_free = true;
 
 			/*
 			 * This code should match the inner loop of mergeonerun().
@@ -1563,11 +1576,11 @@ tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
 				int			tupIndex;
 				SortTuple  *newtup;
 
-				*stup = state->memtuples[0];
+				*ptup = state->memtuples[0];
 				/* returned tuple is no longer counted in our memory space */
-				if (stup->tuple)
+				if (ptup->tuple)
 				{
-					tuplen = GetMemoryChunkSpace(stup->tuple);
+					tuplen = GetMemoryChunkSpace(ptup->tuple);
 					state->availMem += tuplen;
 					state->mergeavailmem[srcTape] += tuplen;
 				}
@@ -1598,6 +1611,8 @@ tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
 				newtup->tupindex = state->mergefreelist;
 				state->mergefreelist = tupIndex;
 				state->mergeavailslots[srcTape]++;
+				if (!stup && dummy.tuple)
+					pfree(dummy.tuple);
 				return true;
 			}
 			return false;
@@ -1620,20 +1635,22 @@ tuplesort_gettupleslot(Tuplesortstate *state, bool forward,
 	MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext);
 	SortTuple	stup;
 	bool		should_free;
+	bool        found;
 
-	if (!tuplesort_gettuple_common(state, forward, &stup, &should_free))
-		stup.tuple = NULL;
+	found = tuplesort_gettuple_common(state, forward, (slot ? &stup : NULL), &should_free);
 
 	MemoryContextSwitchTo(oldcontext);
 
-	if (stup.tuple)
+	if (found)
 	{
-		ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free);
+		if (slot)
+			ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free);
 		return true;
 	}
 	else
 	{
-		ExecClearTuple(slot);
+		if (slot)
+			ExecClearTuple(slot);
 		return false;
 	}
 }
@@ -1692,24 +1709,27 @@ tuplesort_getdatum(Tuplesortstate *state, bool forward,
 	SortTuple	stup;
 	bool		should_free;
 
-	if (!tuplesort_gettuple_common(state, forward, &stup, &should_free))
+	if (!tuplesort_gettuple_common(state, forward, (val ? &stup : NULL), &should_free))
 	{
 		MemoryContextSwitchTo(oldcontext);
 		return false;
 	}
 
-	if (stup.isnull1 || state->datumTypeByVal)
+	if (val)
 	{
-		*val = stup.datum1;
-		*isNull = stup.isnull1;
-	}
-	else
-	{
-		if (should_free)
+		if (stup.isnull1 || state->datumTypeByVal)
+		{
 			*val = stup.datum1;
+			*isNull = stup.isnull1;
+		}
 		else
-			*val = datumCopy(stup.datum1, false, state->datumTypeLen);
-		*isNull = false;
+		{
+			if (should_free)
+				*val = stup.datum1;
+			else
+				*val = datumCopy(stup.datum1, false, state->datumTypeLen);
+			*isNull = false;
+		}
 	}
 
 	MemoryContextSwitchTo(oldcontext);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index bc70dd6..8e378bd 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -229,6 +229,7 @@ static void getTableData(TableInfo *tblinfo, int numTables, bool oids);
 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,
@@ -9363,6 +9364,22 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
 }
 
 /*
+ * 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
@@ -11417,15 +11434,22 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 	int			i_aggtransfn;
 	int			i_aggfinalfn;
 	int			i_aggsortop;
+	int			i_aggtranssortop;
+	int			i_hypothetical;
+	int			i_isstrict;
 	int			i_aggtranstype;
 	int			i_agginitval;
 	int			i_convertok;
 	const char *aggtransfn;
 	const char *aggfinalfn;
 	const char *aggsortop;
+	const char *aggtranssortop;
 	const char *aggtranstype;
 	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)
@@ -11441,11 +11465,31 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 	selectSourceSchema(fout, agginfo->aggfn.dobj.namespace->dobj.name);
 
 	/* Get aggregate-specific details */
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 90400)
+	{
+		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, "
+						  "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",
+						  agginfo->aggfn.dobj.catId.oid);
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBuffer(query, "SELECT aggtransfn, "
 						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
 						  "aggsortop::pg_catalog.regoperator, "
+						  "0 as aggtranssortop, "
+						  "false as hypothetical, "
+						  "false as isstrict, "
 						  "agginitval, "
 						  "'t'::boolean AS convertok, "
 						  "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs, "
@@ -11460,6 +11504,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 		appendPQExpBuffer(query, "SELECT aggtransfn, "
 						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
 						  "aggsortop::pg_catalog.regoperator, "
+						  "0 as aggtranssortop, "
+						  "false as hypothetical, "
+						  "false as isstrict, "
 						  "agginitval, "
 						  "'t'::boolean AS convertok "
 						  "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
@@ -11472,6 +11519,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 		appendPQExpBuffer(query, "SELECT aggtransfn, "
 						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
 						  "0 AS aggsortop, "
+						  "0 as aggtranssortop, "
+						  "'f'::boolean as hypothetical, "
+						  "'f'::boolean as isstrict, "
 						  "agginitval, "
 						  "'t'::boolean AS convertok "
 					  "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
@@ -11484,6 +11534,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 		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, "
 						  "agginitval, "
 						  "'t'::boolean AS convertok "
 						  "FROM pg_aggregate "
@@ -11496,6 +11549,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 						  "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, "
 						  "agginitval1 AS agginitval, "
 						  "(aggtransfn2 = 0 and aggtranstype2 = 0 and agginitval2 is null) AS convertok "
 						  "FROM pg_aggregate "
@@ -11508,6 +11564,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 	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_agginitval = PQfnumber(res, "agginitval");
 	i_convertok = PQfnumber(res, "convertok");
@@ -11515,11 +11574,25 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 	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);
 	agginitval = PQgetvalue(res, 0, i_agginitval);
 	convertok = (PQgetvalue(res, 0, i_convertok)[0] == 't');
 
-	if (fout->remoteVersion >= 80400)
+	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;
@@ -11549,36 +11622,58 @@ 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);
+		/*
+		 * 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, "    SFUNC = %s,\n    STYPE = %s",
+		appendPQExpBuffer(details, "\n    SFUNC = %s,\n    STYPE = %s",
 						  fmtId(aggtransfn),
 						  aggtranstype);
 	}
 	else
 	{
 		/* need quotes all around */
-		appendPQExpBuffer(details, "    SFUNC = %s,\n",
+		appendPQExpBuffer(details, "\n    SFUNC = %s,\n",
 						  fmtId(aggtransfn));
 		appendPQExpBuffer(details, "    STYPE = %s",
 						  fmtId(aggtranstype));
 	}
 
-	if (!PQgetisnull(res, 0, i_agginitval))
+	if (strcmp(aggfinalfn, "-") != 0)
 	{
-		appendPQExpBuffer(details, ",\n    INITCOND = ");
-		appendStringLiteralAH(details, agginitval, fout);
+		appendPQExpBuffer(details, "%s\n    FINALFUNC = %s",
+						  (has_comma ? "" : ","),
+						  aggfinalfn);
 	}
 
-	if (strcmp(aggfinalfn, "-") != 0)
+	if (hypothetical)
+		appendPQExpBuffer(details, ",\n    HYPOTHETICAL");
+
+	if (isstrict)
+		appendPQExpBuffer(details, ",\n    STRICT");
+
+	if (!PQgetisnull(res, 0, i_agginitval))
 	{
-		appendPQExpBuffer(details, ",\n    FINALFUNC = %s",
-						  aggfinalfn);
+		appendPQExpBuffer(details, ",\n    INITCOND = ");
+		appendStringLiteralAH(details, agginitval, fout);
 	}
 
 	aggsortop = convertOperatorReference(fout, aggsortop);
@@ -11588,6 +11683,13 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 						  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
 	 */
@@ -11595,7 +11697,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 					  fmtId(agginfo->aggfn.dobj.namespace->dobj.name),
 					  aggsig);
 
-	appendPQExpBuffer(q, "CREATE AGGREGATE %s (\n%s\n);\n",
+	appendPQExpBuffer(q, "CREATE AGGREGATE %s (%s\n);\n",
 					  aggfullsig, details->data);
 
 	appendPQExpBuffer(labelq, "AGGREGATE %s", aggsig);
@@ -11624,7 +11726,7 @@ 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.
+	 * syntax for zero-argument aggregates and ordered set functions.
 	 */
 	free(aggsig);
 	free(aggsig_tag);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ed1c5fd..ee2a951 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -72,7 +72,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
 					  gettext_noop("Name"),
 					  gettext_noop("Result data type"));
 
-	if (pset.sversion >= 80400)
+	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,7 +258,26 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"));
 
-	if (pset.sversion >= 80400)
+	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"
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 5ad6ea6..b546ca4 100644
--- a/src/include/catalog/pg_aggregate.h
+++ b/src/include/catalog/pg_aggregate.h
@@ -32,6 +32,9 @@
  *	aggfinalfn			final function (0 if none)
  *	aggsortop			associated sort operator (0 if none)
  *	aggtranstype		type of aggregate's transition (state) data
+ *	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)
  * ----------------------------------------------------------------
  */
@@ -44,6 +47,9 @@ CATALOG(pg_aggregate,2600) BKI_WITHOUT_OIDS
 	regproc		aggfinalfn;
 	Oid			aggsortop;
 	Oid			aggtranstype;
+	Oid			aggtranssortop;
+	int32		aggordnargs;
+	bool		aggisordsetfunc;
 
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	text		agginitval;
@@ -62,13 +68,16 @@ typedef FormData_pg_aggregate *Form_pg_aggregate;
  * ----------------
  */
 
-#define Natts_pg_aggregate				6
+#define Natts_pg_aggregate				9
 #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_agginitval	6
+#define Anum_pg_aggregate_aggtranssortop	6
+#define Anum_pg_aggregate_aggordnargs	7
+#define Anum_pg_aggregate_aggisordsetfunc	8
+#define Anum_pg_aggregate_agginitval	9
 
 
 /* ----------------
@@ -77,163 +86,176 @@ typedef FormData_pg_aggregate *Form_pg_aggregate;
  */
 
 /* avg */
-DATA(insert ( 2100	int8_avg_accum	numeric_avg		0	1231	"{0,0}" ));
-DATA(insert ( 2101	int4_avg_accum	int8_avg		0	1016	"{0,0}" ));
-DATA(insert ( 2102	int2_avg_accum	int8_avg		0	1016	"{0,0}" ));
-DATA(insert ( 2103	numeric_avg_accum	numeric_avg		0	1231	"{0,0}" ));
-DATA(insert ( 2104	float4_accum	float8_avg		0	1022	"{0,0,0}" ));
-DATA(insert ( 2105	float8_accum	float8_avg		0	1022	"{0,0,0}" ));
-DATA(insert ( 2106	interval_accum	interval_avg	0	1187	"{0 second,0 second}" ));
+DATA(insert ( 2100	int8_avg_accum	numeric_avg		0	1231	0 -1 f "{0,0}" ));
+DATA(insert ( 2101	int4_avg_accum	int8_avg		0	1016	0 -1 f "{0,0}" ));
+DATA(insert ( 2102	int2_avg_accum	int8_avg		0	1016	0 -1 f "{0,0}" ));
+DATA(insert ( 2103	numeric_avg_accum	numeric_avg		0 1231	0 -1 f "{0,0}" ));
+DATA(insert ( 2104	float4_accum	float8_avg		0	1022	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2105	float8_accum	float8_avg		0	1022	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2106	interval_accum	interval_avg	0	1187	0 -1 f "{0 second,0 second}" ));
 
 /* sum */
-DATA(insert ( 2107	int8_sum		-				0	1700	_null_ ));
-DATA(insert ( 2108	int4_sum		-				0	20		_null_ ));
-DATA(insert ( 2109	int2_sum		-				0	20		_null_ ));
-DATA(insert ( 2110	float4pl		-				0	700		_null_ ));
-DATA(insert ( 2111	float8pl		-				0	701		_null_ ));
-DATA(insert ( 2112	cash_pl			-				0	790		_null_ ));
-DATA(insert ( 2113	interval_pl		-				0	1186	_null_ ));
-DATA(insert ( 2114	numeric_add		-				0	1700	_null_ ));
+DATA(insert ( 2107	int8_sum		-				0	1700	0 -1 f _null_ ));
+DATA(insert ( 2108	int4_sum		-				0	20		0 -1 f _null_ ));
+DATA(insert ( 2109	int2_sum		-				0	20		0 -1 f _null_ ));
+DATA(insert ( 2110	float4pl		-				0	700		0 -1 f _null_ ));
+DATA(insert ( 2111	float8pl		-				0	701		0 -1 f _null_ ));
+DATA(insert ( 2112	cash_pl			-				0	790		0 -1 f _null_ ));
+DATA(insert ( 2113	interval_pl		-				0	1186	0 -1 f _null_ ));
+DATA(insert ( 2114	numeric_add		-				0	1700	0 -1 f _null_ ));
 
 /* max */
-DATA(insert ( 2115	int8larger		-				413		20		_null_ ));
-DATA(insert ( 2116	int4larger		-				521		23		_null_ ));
-DATA(insert ( 2117	int2larger		-				520		21		_null_ ));
-DATA(insert ( 2118	oidlarger		-				610		26		_null_ ));
-DATA(insert ( 2119	float4larger	-				623		700		_null_ ));
-DATA(insert ( 2120	float8larger	-				674		701		_null_ ));
-DATA(insert ( 2121	int4larger		-				563		702		_null_ ));
-DATA(insert ( 2122	date_larger		-				1097	1082	_null_ ));
-DATA(insert ( 2123	time_larger		-				1112	1083	_null_ ));
-DATA(insert ( 2124	timetz_larger	-				1554	1266	_null_ ));
-DATA(insert ( 2125	cashlarger		-				903		790		_null_ ));
-DATA(insert ( 2126	timestamp_larger	-			2064	1114	_null_ ));
-DATA(insert ( 2127	timestamptz_larger	-			1324	1184	_null_ ));
-DATA(insert ( 2128	interval_larger -				1334	1186	_null_ ));
-DATA(insert ( 2129	text_larger		-				666		25		_null_ ));
-DATA(insert ( 2130	numeric_larger	-				1756	1700	_null_ ));
-DATA(insert ( 2050	array_larger	-				1073	2277	_null_ ));
-DATA(insert ( 2244	bpchar_larger	-				1060	1042	_null_ ));
-DATA(insert ( 2797	tidlarger		-				2800	27		_null_ ));
-DATA(insert ( 3526	enum_larger		-				3519	3500	_null_ ));
+DATA(insert ( 2115	int8larger		-				413		20		0 -1 f _null_ ));
+DATA(insert ( 2116	int4larger		-				521		23		0 -1 f _null_ ));
+DATA(insert ( 2117	int2larger		-				520		21		0 -1 f _null_ ));
+DATA(insert ( 2118	oidlarger		-				610		26		0 -1 f _null_ ));
+DATA(insert ( 2119	float4larger	-				623		700		0 -1 f _null_ ));
+DATA(insert ( 2120	float8larger	-				674		701		0 -1 f _null_ ));
+DATA(insert ( 2121	int4larger		-				563		702		0 -1 f _null_ ));
+DATA(insert ( 2122	date_larger		-				1097	1082	0 -1 f _null_ ));
+DATA(insert ( 2123	time_larger		-				1112	1083	0 -1 f _null_ ));
+DATA(insert ( 2124	timetz_larger	-				1554	1266	0 -1 f _null_ ));
+DATA(insert ( 2125	cashlarger		-				903		790		0 -1 f _null_ ));
+DATA(insert ( 2126	timestamp_larger	-			2064	1114	0 -1 f _null_ ));
+DATA(insert ( 2127	timestamptz_larger	-			1324	1184	0 -1 f _null_ ));
+DATA(insert ( 2128	interval_larger -				1334	1186	0 -1 f _null_ ));
+DATA(insert ( 2129	text_larger		-				666		25		0 -1 f _null_ ));
+DATA(insert ( 2130	numeric_larger	-				1756	1700	0 -1 f _null_ ));
+DATA(insert ( 2050	array_larger	-				1073	2277	0 -1 f _null_ ));
+DATA(insert ( 2244	bpchar_larger	-				1060	1042	0 -1 f _null_ ));
+DATA(insert ( 2797	tidlarger		-				2800	27		0 -1 f _null_ ));
+DATA(insert ( 3526	enum_larger		-				3519	3500	0 -1 f _null_ ));
 
 /* min */
-DATA(insert ( 2131	int8smaller		-				412		20		_null_ ));
-DATA(insert ( 2132	int4smaller		-				97		23		_null_ ));
-DATA(insert ( 2133	int2smaller		-				95		21		_null_ ));
-DATA(insert ( 2134	oidsmaller		-				609		26		_null_ ));
-DATA(insert ( 2135	float4smaller	-				622		700		_null_ ));
-DATA(insert ( 2136	float8smaller	-				672		701		_null_ ));
-DATA(insert ( 2137	int4smaller		-				562		702		_null_ ));
-DATA(insert ( 2138	date_smaller	-				1095	1082	_null_ ));
-DATA(insert ( 2139	time_smaller	-				1110	1083	_null_ ));
-DATA(insert ( 2140	timetz_smaller	-				1552	1266	_null_ ));
-DATA(insert ( 2141	cashsmaller		-				902		790		_null_ ));
-DATA(insert ( 2142	timestamp_smaller	-			2062	1114	_null_ ));
-DATA(insert ( 2143	timestamptz_smaller -			1322	1184	_null_ ));
-DATA(insert ( 2144	interval_smaller	-			1332	1186	_null_ ));
-DATA(insert ( 2145	text_smaller	-				664		25		_null_ ));
-DATA(insert ( 2146	numeric_smaller -				1754	1700	_null_ ));
-DATA(insert ( 2051	array_smaller	-				1072	2277	_null_ ));
-DATA(insert ( 2245	bpchar_smaller	-				1058	1042	_null_ ));
-DATA(insert ( 2798	tidsmaller		-				2799	27		_null_ ));
-DATA(insert ( 3527	enum_smaller	-				3518	3500	_null_ ));
+DATA(insert ( 2131	int8smaller		-				412		20		0 -1 f _null_ ));
+DATA(insert ( 2132	int4smaller		-				97		23		0 -1 f _null_ ));
+DATA(insert ( 2133	int2smaller		-				95		21		0 -1 f _null_ ));
+DATA(insert ( 2134	oidsmaller		-				609		26		0 -1 f _null_ ));
+DATA(insert ( 2135	float4smaller	-				622		700		0 -1 f _null_ ));
+DATA(insert ( 2136	float8smaller	-				672		701		0 -1 f _null_ ));
+DATA(insert ( 2137	int4smaller		-				562		702		0 -1 f _null_ ));
+DATA(insert ( 2138	date_smaller	-				1095	1082	0 -1 f _null_ ));
+DATA(insert ( 2139	time_smaller	-				1110	1083	0 -1 f _null_ ));
+DATA(insert ( 2140	timetz_smaller	-				1552	1266	0 -1 f _null_ ));
+DATA(insert ( 2141	cashsmaller		-				902		790		0 -1 f _null_ ));
+DATA(insert ( 2142	timestamp_smaller	-			2062	1114	0 -1 f _null_ ));
+DATA(insert ( 2143	timestamptz_smaller -			1322	1184	0 -1 f _null_ ));
+DATA(insert ( 2144	interval_smaller	-			1332	1186	0 -1 f _null_ ));
+DATA(insert ( 2145	text_smaller	-				664		25		0 -1 f _null_ ));
+DATA(insert ( 2146	numeric_smaller -				1754	1700	0 -1 f _null_ ));
+DATA(insert ( 2051	array_smaller	-				1072	2277	0 -1 f _null_ ));
+DATA(insert ( 2245	bpchar_smaller	-				1058	1042	0 -1 f _null_ ));
+DATA(insert ( 2798	tidsmaller		-				2799	27		0 -1 f _null_ ));
+DATA(insert ( 3527	enum_smaller	-				3518	3500	0 -1 f _null_ ));
 
 /* count */
-DATA(insert ( 2147	int8inc_any		-				0		20		"0" ));
-DATA(insert ( 2803	int8inc			-				0		20		"0" ));
+DATA(insert ( 2147	int8inc_any		-				0		20		0 -1 f "0" ));
+DATA(insert ( 2803	int8inc			-				0		20		0 -1 f "0" ));
 
 /* var_pop */
-DATA(insert ( 2718	int8_accum	numeric_var_pop 0	1231	"{0,0,0}" ));
-DATA(insert ( 2719	int4_accum	numeric_var_pop 0	1231	"{0,0,0}" ));
-DATA(insert ( 2720	int2_accum	numeric_var_pop 0	1231	"{0,0,0}" ));
-DATA(insert ( 2721	float4_accum	float8_var_pop 0	1022	"{0,0,0}" ));
-DATA(insert ( 2722	float8_accum	float8_var_pop 0	1022	"{0,0,0}" ));
-DATA(insert ( 2723	numeric_accum  numeric_var_pop 0	1231	"{0,0,0}" ));
+DATA(insert ( 2718	int8_accum	numeric_var_pop 0	1231	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2719	int4_accum	numeric_var_pop 0	1231	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2720	int2_accum	numeric_var_pop 0	1231	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2721	float4_accum	float8_var_pop 0	1022	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2722	float8_accum	float8_var_pop 0	1022	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2723	numeric_accum  numeric_var_pop 0	1231	0 -1 f "{0,0,0}" ));
 
 /* var_samp */
-DATA(insert ( 2641	int8_accum	numeric_var_samp	0	1231	"{0,0,0}" ));
-DATA(insert ( 2642	int4_accum	numeric_var_samp	0	1231	"{0,0,0}" ));
-DATA(insert ( 2643	int2_accum	numeric_var_samp	0	1231	"{0,0,0}" ));
-DATA(insert ( 2644	float4_accum	float8_var_samp 0	1022	"{0,0,0}" ));
-DATA(insert ( 2645	float8_accum	float8_var_samp 0	1022	"{0,0,0}" ));
-DATA(insert ( 2646	numeric_accum  numeric_var_samp 0	1231	"{0,0,0}" ));
+DATA(insert ( 2641	int8_accum	numeric_var_samp	0	1231	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2642	int4_accum	numeric_var_samp	0	1231	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2643	int2_accum	numeric_var_samp	0	1231	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2644	float4_accum	float8_var_samp 0	1022	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2645	float8_accum	float8_var_samp 0	1022	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2646	numeric_accum  numeric_var_samp 0	1231	0 -1 f "{0,0,0}" ));
 
 /* variance: historical Postgres syntax for var_samp */
-DATA(insert ( 2148	int8_accum	numeric_var_samp	0	1231	"{0,0,0}" ));
-DATA(insert ( 2149	int4_accum	numeric_var_samp	0	1231	"{0,0,0}" ));
-DATA(insert ( 2150	int2_accum	numeric_var_samp	0	1231	"{0,0,0}" ));
-DATA(insert ( 2151	float4_accum	float8_var_samp 0	1022	"{0,0,0}" ));
-DATA(insert ( 2152	float8_accum	float8_var_samp 0	1022	"{0,0,0}" ));
-DATA(insert ( 2153	numeric_accum  numeric_var_samp 0	1231	"{0,0,0}" ));
+DATA(insert ( 2148	int8_accum	numeric_var_samp	0	1231	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2149	int4_accum	numeric_var_samp	0	1231	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2150	int2_accum	numeric_var_samp	0	1231	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2151	float4_accum	float8_var_samp 0	1022	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2152	float8_accum	float8_var_samp 0	1022	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2153	numeric_accum  numeric_var_samp 0	1231	0 -1 f "{0,0,0}" ));
 
 /* stddev_pop */
-DATA(insert ( 2724	int8_accum	numeric_stddev_pop		0	1231	"{0,0,0}" ));
-DATA(insert ( 2725	int4_accum	numeric_stddev_pop		0	1231	"{0,0,0}" ));
-DATA(insert ( 2726	int2_accum	numeric_stddev_pop		0	1231	"{0,0,0}" ));
-DATA(insert ( 2727	float4_accum	float8_stddev_pop	0	1022	"{0,0,0}" ));
-DATA(insert ( 2728	float8_accum	float8_stddev_pop	0	1022	"{0,0,0}" ));
-DATA(insert ( 2729	numeric_accum	numeric_stddev_pop	0	1231	"{0,0,0}" ));
+DATA(insert ( 2724	int8_accum	numeric_stddev_pop		0	1231	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2725	int4_accum	numeric_stddev_pop		0	1231	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2726	int2_accum	numeric_stddev_pop		0	1231	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2727	float4_accum	float8_stddev_pop	0	1022	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2728	float8_accum	float8_stddev_pop	0	1022	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2729	numeric_accum	numeric_stddev_pop	0	1231	0 -1 f "{0,0,0}" ));
 
 /* stddev_samp */
-DATA(insert ( 2712	int8_accum	numeric_stddev_samp		0	1231	"{0,0,0}" ));
-DATA(insert ( 2713	int4_accum	numeric_stddev_samp		0	1231	"{0,0,0}" ));
-DATA(insert ( 2714	int2_accum	numeric_stddev_samp		0	1231	"{0,0,0}" ));
-DATA(insert ( 2715	float4_accum	float8_stddev_samp	0	1022	"{0,0,0}" ));
-DATA(insert ( 2716	float8_accum	float8_stddev_samp	0	1022	"{0,0,0}" ));
-DATA(insert ( 2717	numeric_accum	numeric_stddev_samp 0	1231	"{0,0,0}" ));
+DATA(insert ( 2712	int8_accum	numeric_stddev_samp		0	1231	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2713	int4_accum	numeric_stddev_samp		0	1231	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2714	int2_accum	numeric_stddev_samp		0	1231	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2715	float4_accum	float8_stddev_samp	0	1022	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2716	float8_accum	float8_stddev_samp	0	1022	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2717	numeric_accum	numeric_stddev_samp 0	1231	0 -1 f "{0,0,0}" ));
 
 /* stddev: historical Postgres syntax for stddev_samp */
-DATA(insert ( 2154	int8_accum	numeric_stddev_samp		0	1231	"{0,0,0}" ));
-DATA(insert ( 2155	int4_accum	numeric_stddev_samp		0	1231	"{0,0,0}" ));
-DATA(insert ( 2156	int2_accum	numeric_stddev_samp		0	1231	"{0,0,0}" ));
-DATA(insert ( 2157	float4_accum	float8_stddev_samp	0	1022	"{0,0,0}" ));
-DATA(insert ( 2158	float8_accum	float8_stddev_samp	0	1022	"{0,0,0}" ));
-DATA(insert ( 2159	numeric_accum	numeric_stddev_samp 0	1231	"{0,0,0}" ));
+DATA(insert ( 2154	int8_accum	numeric_stddev_samp		0	1231	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2155	int4_accum	numeric_stddev_samp		0	1231	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2156	int2_accum	numeric_stddev_samp		0	1231	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2157	float4_accum	float8_stddev_samp	0	1022	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2158	float8_accum	float8_stddev_samp	0	1022	0 -1 f "{0,0,0}" ));
+DATA(insert ( 2159	numeric_accum	numeric_stddev_samp 0	1231	0 -1 f "{0,0,0}" ));
 
 /* SQL2003 binary regression aggregates */
-DATA(insert ( 2818	int8inc_float8_float8		-				0	20		"0" ));
-DATA(insert ( 2819	float8_regr_accum	float8_regr_sxx			0	1022	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2820	float8_regr_accum	float8_regr_syy			0	1022	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2821	float8_regr_accum	float8_regr_sxy			0	1022	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2822	float8_regr_accum	float8_regr_avgx		0	1022	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2823	float8_regr_accum	float8_regr_avgy		0	1022	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2824	float8_regr_accum	float8_regr_r2			0	1022	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2825	float8_regr_accum	float8_regr_slope		0	1022	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2826	float8_regr_accum	float8_regr_intercept	0	1022	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2827	float8_regr_accum	float8_covar_pop		0	1022	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2828	float8_regr_accum	float8_covar_samp		0	1022	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2829	float8_regr_accum	float8_corr				0	1022	"{0,0,0,0,0,0}" ));
+DATA(insert ( 2818	int8inc_float8_float8		-				0	20		0 -1 f "0" ));
+DATA(insert ( 2819	float8_regr_accum	float8_regr_sxx			0	1022	0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2820	float8_regr_accum	float8_regr_syy			0	1022	0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2821	float8_regr_accum	float8_regr_sxy			0	1022	0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2822	float8_regr_accum	float8_regr_avgx		0	1022	0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2823	float8_regr_accum	float8_regr_avgy		0	1022	0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2824	float8_regr_accum	float8_regr_r2			0	1022	0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2825	float8_regr_accum	float8_regr_slope		0	1022	0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2826	float8_regr_accum	float8_regr_intercept	0	1022	0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2827	float8_regr_accum	float8_covar_pop		0	1022	0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2828	float8_regr_accum	float8_covar_samp		0	1022	0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2829	float8_regr_accum	float8_corr				0	1022	0 -1 f "{0,0,0,0,0,0}" ));
 
 /* boolean-and and boolean-or */
-DATA(insert ( 2517	booland_statefunc	-			58	16		_null_ ));
-DATA(insert ( 2518	boolor_statefunc	-			59	16		_null_ ));
-DATA(insert ( 2519	booland_statefunc	-			58	16		_null_ ));
+DATA(insert ( 2517	booland_statefunc	-			58	16		0 -1 f _null_ ));
+DATA(insert ( 2518	boolor_statefunc	-			59	16		0 -1 f _null_ ));
+DATA(insert ( 2519	booland_statefunc	-			58	16		0 -1 f _null_ ));
 
 /* bitwise integer */
-DATA(insert ( 2236 int2and		  -					0	21		_null_ ));
-DATA(insert ( 2237 int2or		  -					0	21		_null_ ));
-DATA(insert ( 2238 int4and		  -					0	23		_null_ ));
-DATA(insert ( 2239 int4or		  -					0	23		_null_ ));
-DATA(insert ( 2240 int8and		  -					0	20		_null_ ));
-DATA(insert ( 2241 int8or		  -					0	20		_null_ ));
-DATA(insert ( 2242 bitand		  -					0	1560	_null_ ));
-DATA(insert ( 2243 bitor		  -					0	1560	_null_ ));
+DATA(insert ( 2236 int2and		  -					0	21		0 -1 f _null_ ));
+DATA(insert ( 2237 int2or		  -					0	21		0 -1 f _null_ ));
+DATA(insert ( 2238 int4and		  -					0	23		0 -1 f _null_ ));
+DATA(insert ( 2239 int4or		  -					0	23		0 -1 f _null_ ));
+DATA(insert ( 2240 int8and		  -					0	20		0 -1 f _null_ ));
+DATA(insert ( 2241 int8or		  -					0	20		0 -1 f _null_ ));
+DATA(insert ( 2242 bitand		  -					0	1560	0 -1 f _null_ ));
+DATA(insert ( 2243 bitor		  -					0	1560	0 -1 f _null_ ));
 
 /* xml */
-DATA(insert ( 2901 xmlconcat2	  -					0	142		_null_ ));
+DATA(insert ( 2901 xmlconcat2	  -					0	142		0 -1 f _null_ ));
 
 /* array */
-DATA(insert ( 2335	array_agg_transfn	array_agg_finalfn		0	2281	_null_ ));
+DATA(insert ( 2335	array_agg_transfn	array_agg_finalfn		0	2281	0 -1 f _null_ ));
 
 /* text */
-DATA(insert ( 3538	string_agg_transfn	string_agg_finalfn		0	2281	_null_ ));
+DATA(insert ( 3538	string_agg_transfn	string_agg_finalfn		0	2281	0 -1 f _null_ ));
 
 /* bytea */
-DATA(insert ( 3545	bytea_string_agg_transfn	bytea_string_agg_finalfn		0	2281	_null_ ));
+DATA(insert ( 3545	bytea_string_agg_transfn	bytea_string_agg_finalfn		0	2281	0 -1 f _null_ ));
 
 /* json */
-DATA(insert ( 3175	json_agg_transfn	json_agg_finalfn		0	2281	_null_ ));
+DATA(insert ( 3175	json_agg_transfn	json_agg_finalfn		0	2281	0 -1 f _null_ ));
+
+/* ordered set functions */
+DATA(insert ( 3931 	- 			percentile_disc_final				0 	0 	0	1 t _null_));
+DATA(insert ( 3935 	- 			percentile_cont_float8_final		0 	0 	0	1 t _null_));
+DATA(insert ( 3939 	- 			percentile_cont_interval_final		0 	0 	0	1 t _null_));
+DATA(insert ( 3920 	- 			rank_final							0 	16 	59 -2 t "f"));
+DATA(insert ( 3970 	- 			dense_rank_final					0 	16 	59 -2 t "f"));
+DATA(insert ( 3972 	- 			percent_rank_final					0 	16 	59 -2 t "f"));
+DATA(insert ( 3974 	- 			cume_dist_final						0 	16 	58 -2 t "f"));
+DATA(insert ( 3976 	- 			mode_final							0 	0 	0	0 t _null_));
+DATA(insert ( 3978 	- 			percentile_disc_multi_final				0 	0 	0	1 t _null_));
+DATA(insert ( 3980 	- 			percentile_cont_float8_multi_final		0 	0 	0	1 t _null_));
+DATA(insert ( 3982 	- 			percentile_cont_interval_multi_final	0 	0 	0	1 t _null_));
 
 /*
  * prototypes for functions in pg_aggregate.c
@@ -241,6 +263,7 @@ DATA(insert ( 3175	json_agg_transfn	json_agg_finalfn		0	2281	_null_ ));
 extern Oid AggregateCreate(const char *aggName,
 				Oid aggNamespace,
 				int numArgs,
+				int numDirectArgs,
 				oidvector *parameterTypes,
 				Datum allParameterTypes,
 				Datum parameterModes,
@@ -249,7 +272,11 @@ extern Oid AggregateCreate(const char *aggName,
 				List *aggtransfnName,
 				List *aggfinalfnName,
 				List *aggsortopName,
+				List *aggtranssortopName,
 				Oid aggTransType,
-				const char *agginitval);
+				const char *agginitval,
+				bool isStrict,
+				bool isOrderedSetFunc,
+				bool isHypotheticalSet);
 
 #endif   /* PG_AGGREGATE_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ca4fc62..a0d2e12 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1973,6 +1973,10 @@ DATA(insert OID = 2232 (  pg_get_function_identity_arguments	   PGNSP PGUID 12 1
 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 = 3178 (  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 = 3179 (  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");
@@ -4750,6 +4754,74 @@ DESCR("SP-GiST support for quad tree over range");
 /* 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,
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 1f72e1b..50ee3de 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -651,6 +651,35 @@ extern void **find_rendezvous_variable(const char *varName);
 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
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 3b430e0..ba9f328 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -588,6 +588,7 @@ typedef struct AggrefExprState
 {
 	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;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 78368c6..c6035e0 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -425,7 +425,8 @@ 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 */
+	T_FdwRoutine,				/* in foreign/fdwapi.h */
+	T_AggStatePerAggData			/* private in nodeAgg.c */
 } NodeTag;
 
 /*
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e5235cb..414bb98 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -303,6 +303,7 @@ typedef struct FuncCall
 	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;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 7918537..594dddf 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -247,9 +247,12 @@ typedef struct Aggref
 	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;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 8bd34d6..ab27156 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -412,6 +412,7 @@ PG_KEYWORD("where", WHERE, RESERVED_KEYWORD)
 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)
diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h
index b6d9dd3..5fe6295 100644
--- a/src/include/parser/parse_agg.h
+++ b/src/include/parser/parse_agg.h
@@ -17,7 +17,7 @@
 
 extern void transformAggregateCall(ParseState *pstate, Aggref *agg,
 					   List *args, List *aggorder,
-					   bool agg_distinct);
+					   bool agg_distinct, bool agg_within_group);
 extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 						WindowDef *windef);
 
@@ -34,4 +34,18 @@ extern void build_aggregate_fnexprs(Oid *agg_input_types,
 						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 */
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index 9bdb033..dcc08d0 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -31,7 +31,7 @@ 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);
+					bool resolveUnknown, bool useSQL99, bool keepDuplicates);
 
 extern List *transformWindowDefinitions(ParseState *pstate,
 						   List *windowdefs,
diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h
index d33eef3..834fc92 100644
--- a/src/include/parser/parse_func.h
+++ b/src/include/parser/parse_func.h
@@ -38,14 +38,11 @@ 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 */
+	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);
+							   int location, FuncCall *fn);
 
 extern FuncDetailCode func_get_detail(List *funcname,
 				List *fargs, List *fargnames,
@@ -66,8 +63,10 @@ extern FuncCandidateList func_select_candidate(int nargs,
 
 extern void make_fn_arguments(ParseState *pstate,
 				  List *fargs,
+				  List *agg_order,
 				  Oid *actual_arg_types,
-				  Oid *declared_arg_types);
+				  Oid *declared_arg_types,
+				  bool requiresUnification);
 
 extern const char *funcname_signature_string(const char *funcname, int nargs,
 						  List *argnames, const Oid *argtypes);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index ce3f00b..8cda3d9 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -665,6 +665,8 @@ extern Datum pg_get_functiondef(PG_FUNCTION_ARGS);
 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);
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 1af79e5..990de50 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1249,6 +1249,213 @@ select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
  {"(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 only 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)
+
+-- 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     
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 57d614f..c72bd78 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -700,9 +700,17 @@ SELECT * FROM funcdescs
 
 -- **************** 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 aggtransfn = 0 OR aggtranstype = 0;
+WHERE aggfnoid = 0
+   OR CASE WHEN aggisordsetfunc
+           THEN aggtransfn <> 0 OR aggfinalfn = 0
+           ELSE aggtransfn = 0 OR aggtranstype = 0
+      END;
  ctid | aggfnoid 
 ------+----------
 (0 rows)
@@ -764,8 +772,9 @@ 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]));
+     OR (aggisordsetfunc IS FALSE
+         AND (pfn.pronargs != 1
+              OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]))));
  aggfnoid | proname | oid | proname 
 ----------+---------+-----+---------
 (0 rows)
@@ -857,10 +866,12 @@ 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;
+-- 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)
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 397edff..6e6a2db 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -481,6 +481,69 @@ select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
     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;
+
+-- 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;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index efcd70f..0b2cba7 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -564,10 +564,18 @@ SELECT * FROM funcdescs
 -- **************** 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 aggtransfn = 0 OR aggtranstype = 0;
+WHERE aggfnoid = 0
+   OR CASE WHEN aggisordsetfunc
+           THEN aggtransfn <> 0 OR aggfinalfn = 0
+           ELSE aggtransfn = 0 OR aggtranstype = 0
+      END;
 
 -- Make sure the matching pg_proc entry is sensible, too.
 
@@ -618,8 +626,9 @@ 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]));
+     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,11 +694,13 @@ 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.
+-- 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 oid, proname
-FROM pg_proc AS p
-WHERE proisagg AND provariadic != 0;
+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.
 
