Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

Started by Andrew Gierthover 12 years ago28 messages
#1Andrew Gierth
andrew@tao11.riddles.org.uk

The spec defines two types of aggregate function classed as "ordered set
function", as follows:

1. An "inverse distribution function" taking one argument (which must be
a grouped column or otherwise constant within groups) plus a sorted
group with exactly one column:

=# SELECT (func(p) WITHIN GROUP (ORDER BY q)) from ...

The motivating example for this (and the only ones in the spec) are
percentile_cont and percentile_disc, to return a percentile result
from a continuous or discrete distribution. (Thus
percentile_cont(0.5) within group (order by x) is the spec's version
of a median(x) function.)

2. A "hypothetical set function" taking N arguments of arbitrary types
(a la VARIADIC "any", rather than a fixed list) plus a sorted group
with N columns of matching types:

=# SELECT (func(p1,p2,...) WITHIN GROUP (ORDER BY q1,q2,...)) from ...

(where typeof(p1)==typeof(q1) and so on, at least up to trivial
conversions)

The motivating example here is to be able to do rank(p1,p2,...) to
return the rank that the specified values would have had if they were
added to the group.

As usual, we do not want to constrain ourselves to supporting only the
specific cases in the spec, but would prefer a general solution.

We (meaning myself and Atri) have an implementation that basically
works, though it is not yet complete, but before taking it any further
we need to resolve the design question of how to represent these two
types of function in the system catalogs. The fact that there are in
effect two parts to the parameter list, which are either independent
(for inverse distribution funcs) or closely related (for hypothetical
set functions), doesn't seem to point to an obvious way to represent
this in pg_proc/pg_aggregate.

I'm not yet satisfied with the method used in our implementation, so
we're throwing this open for suggestions. We will post the
work-in-progress patch along with a description of its current
implementation shortly.

One of the major complications is that we ideally want to be able to do
polymorphism based on the type of the sorted group, specifically in
order to be able to do

percentile_disc(float8) within group (order by anyelement)

returning anyelement. (i.e. we should be able to get a discrete
percentile from any type that is orderable.) The question here is how to
resolve the return type both of the aggregate itself and of the finalfn.

We've also had an expression of interest in extending this to allow
percentile_disc(float8[]) and percentile_cont(float8[]) returning
arrays; e.g. percentile_cont(array[0, 0.25, 0.5, 0.75, 1]) to return an
array containing the bounds, median and quartiles in one go. This is an
extension to the spec but it seems sufficiently obviously useful to be
worth supporting.

Comments?

--
Andrew (irc:RhodiumToad)

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#2David Fetter
david@fetter.org
In reply to: Andrew Gierth (#1)
Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

On Thu, Jul 18, 2013 at 03:15:14AM +0000, Andrew Gierth wrote:

The spec defines two types of aggregate function classed as "ordered set
function", as follows:

1. An "inverse distribution function" taking one argument (which must be
a grouped column or otherwise constant within groups) plus a sorted
group with exactly one column:

=# SELECT (func(p) WITHIN GROUP (ORDER BY q)) from ...

The motivating example for this (and the only ones in the spec) are
percentile_cont and percentile_disc, to return a percentile result
from a continuous or discrete distribution. (Thus
percentile_cont(0.5) within group (order by x) is the spec's version
of a median(x) function.)

2. A "hypothetical set function" taking N arguments of arbitrary types
(a la VARIADIC "any", rather than a fixed list) plus a sorted group
with N columns of matching types:

=# SELECT (func(p1,p2,...) WITHIN GROUP (ORDER BY q1,q2,...)) from ...

(where typeof(p1)==typeof(q1) and so on, at least up to trivial
conversions)

The motivating example here is to be able to do rank(p1,p2,...) to
return the rank that the specified values would have had if they were
added to the group.

As usual, we do not want to constrain ourselves to supporting only the
specific cases in the spec, but would prefer a general solution.

We (meaning myself and Atri) have an implementation that basically
works, though it is not yet complete, but before taking it any further
we need to resolve the design question of how to represent these two
types of function in the system catalogs. The fact that there are in
effect two parts to the parameter list, which are either independent
(for inverse distribution funcs) or closely related (for hypothetical
set functions), doesn't seem to point to an obvious way to represent
this in pg_proc/pg_aggregate.

I'm not yet satisfied with the method used in our implementation,

What is that method?

so we're throwing this open for suggestions. We will post the
work-in-progress patch along with a description of its current
implementation shortly.

One of the major complications is that we ideally want to be able to
do polymorphism based on the type of the sorted group, specifically
in order to be able to do

percentile_disc(float8) within group (order by anyelement)

returning anyelement. (i.e. we should be able to get a discrete
percentile from any type that is orderable.) The question here is
how to resolve the return type both of the aggregate itself and of
the finalfn.

We've also had an expression of interest in extending this to allow
percentile_disc(float8[]) and percentile_cont(float8[]) returning
arrays; e.g. percentile_cont(array[0, 0.25, 0.5, 0.75, 1]) to return
an array containing the bounds, median and quartiles in one go. This
is an extension to the spec but it seems sufficiently obviously
useful to be worth supporting.

Comments?

I'm really happy to see PostgreSQL come into its own when it comes to
the analytics side of the house :)

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Atri Sharma
atri.jiit@gmail.com
In reply to: David Fetter (#2)
Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

On Thu, Jul 18, 2013 at 10:02 AM, David Fetter <david@fetter.org> wrote:

On Thu, Jul 18, 2013 at 03:15:14AM +0000, Andrew Gierth wrote:

The spec defines two types of aggregate function classed as "ordered set
function", as follows:

1. An "inverse distribution function" taking one argument (which must be
a grouped column or otherwise constant within groups) plus a sorted
group with exactly one column:

=# SELECT (func(p) WITHIN GROUP (ORDER BY q)) from ...

The motivating example for this (and the only ones in the spec) are
percentile_cont and percentile_disc, to return a percentile result
from a continuous or discrete distribution. (Thus
percentile_cont(0.5) within group (order by x) is the spec's version
of a median(x) function.)

2. A "hypothetical set function" taking N arguments of arbitrary types
(a la VARIADIC "any", rather than a fixed list) plus a sorted group
with N columns of matching types:

=# SELECT (func(p1,p2,...) WITHIN GROUP (ORDER BY q1,q2,...)) from ...

(where typeof(p1)==typeof(q1) and so on, at least up to trivial
conversions)

The motivating example here is to be able to do rank(p1,p2,...) to
return the rank that the specified values would have had if they were
added to the group.

As usual, we do not want to constrain ourselves to supporting only the
specific cases in the spec, but would prefer a general solution.

We (meaning myself and Atri) have an implementation that basically
works, though it is not yet complete, but before taking it any further
we need to resolve the design question of how to represent these two
types of function in the system catalogs. The fact that there are in
effect two parts to the parameter list, which are either independent
(for inverse distribution funcs) or closely related (for hypothetical
set functions), doesn't seem to point to an obvious way to represent
this in pg_proc/pg_aggregate.

I'm not yet satisfied with the method used in our implementation,

What is that method?

We currently represent ordered set functions with a new bool flag in
pg_aggregate. The flag is set to true for ordered set
functions(obviously) and false for all others. The currently
implemented functions i.e. percentile_disc, percentile_cont and
percentile_cont for intervals have their finalfns present in
pg_aggregate.

The aggregate functions take in two arguments, one for the percentile
value and other for the input row set. So, percentile_cont's entry in
pg_proc has float8 and float8 as its parameters and another entry of
percentile_cont (with the interval version as the finalfn) has float8
and interval as its parameter types.

As you can see, there isn't a way right now to resolve the return type
of the aggregate for polymorphic cases. This is something we wish to
resolve.

Regards,

Atri

--
Regards,

Atri
l'apprenant

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Josh Berkus
josh@agliodbs.com
In reply to: Andrew Gierth (#1)
Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

On 07/17/2013 08:15 PM, Andrew Gierth wrote:

The spec defines two types of aggregate function classed as "ordered set
function", as follows:

1. An "inverse distribution function" taking one argument (which must be
a grouped column or otherwise constant within groups) plus a sorted
group with exactly one column:

=# SELECT (func(p) WITHIN GROUP (ORDER BY q)) from ...

The motivating example for this (and the only ones in the spec) are
percentile_cont and percentile_disc, to return a percentile result
from a continuous or discrete distribution. (Thus
percentile_cont(0.5) within group (order by x) is the spec's version
of a median(x) function.)

One question is how this relates to the existing

SELECT agg_func(x order by y)

... syntax. Clearly there's some extra functionality here, but the two
are very similar conceptually.

2. A "hypothetical set function" taking N arguments of arbitrary types
(a la VARIADIC "any", rather than a fixed list) plus a sorted group
with N columns of matching types:

=# SELECT (func(p1,p2,...) WITHIN GROUP (ORDER BY q1,q2,...)) from ...

(where typeof(p1)==typeof(q1) and so on, at least up to trivial
conversions)

The motivating example here is to be able to do rank(p1,p2,...) to
return the rank that the specified values would have had if they were
added to the group.

Wow, I can't possibly grasp the purpose of this. Maybe a practical example?

We've also had an expression of interest in extending this to allow
percentile_disc(float8[]) and percentile_cont(float8[]) returning
arrays; e.g. percentile_cont(array[0, 0.25, 0.5, 0.75, 1]) to return an
array containing the bounds, median and quartiles in one go. This is an
extension to the spec but it seems sufficiently obviously useful to be
worth supporting.

To be specific, I asked for this because it's already something I do
using PL/R, although in PL/R it's pretty much limited to floats.

Anyway, for anyone who isn't following why we want this: statitical
summary reports. For example, I'd love to be able to do a quartile
distribution of query execution times without resorting to R.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Andrew Gierth (#1)
Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

Josh Berkus wrote:

On 07/17/2013 08:15 PM, Andrew Gierth wrote:

The spec defines two types of aggregate function classed as "ordered set
function", as follows:

1. An "inverse distribution function" taking one argument (which must be
a grouped column or otherwise constant within groups) plus a sorted
group with exactly one column:

=# SELECT (func(p) WITHIN GROUP (ORDER BY q)) from ...

The motivating example for this (and the only ones in the spec) are
percentile_cont and percentile_disc, to return a percentile result
from a continuous or discrete distribution. (Thus
percentile_cont(0.5) within group (order by x) is the spec's version
of a median(x) function.)

One question is how this relates to the existing

SELECT agg_func(x order by y)

... syntax. Clearly there's some extra functionality here, but the two
are very similar conceptually.

Well, as you probably know, the spec is a whole pile of random
special-case syntax and any similarities are probably more accidental
than anything else.

A major difference is that in agg(x order by y), the values of y are
not passed to the aggregate function - they serve no purpose other
than controlling the order of the "x" values. Whereas in WITHIN GROUP,
the values in the ORDER BY ... clause are in some sense the primary
input to the aggregate, and the "p" argument is secondary and can't
vary between rows of the group.

Our implementation does heavily reuse the existing executor mechanics
for ORDER BY in aggregates, and it also reuses a fair chunk of the
parser code for it, but there are significant differences.

[of hypothetical set functions]
Wow, I can't possibly grasp the purpose of this. Maybe a practical
example?

=# select rank(123) within group (order by x)
from (values (10),(50),(100),(200),(500)) v(x);

would return 1 row containing the value 4, because if you added the
value 123 to the grouped values, it would have been ranked 4th.

Any time you want to calculate what the rank, dense_rank or cume_dist
would be of a specific row within a group without actually adding the
row to the group, this is how it's done.

I don't have any practical examples to hand, but this beast seems to
be implemented in at least Oracle and MSSQL so I guess it has uses.

[on supporting arrays of percentiles]
To be specific, I asked for this because it's already something I do
using PL/R, although in PL/R it's pretty much limited to floats.

percentile_cont is limited to floats and intervals in the spec; to be
precise, it's limited to taking args of either interval or any numeric
type, and returns interval for interval args, and "approximate numeric
with implementation-defined precision", i.e. some form of float, for
numeric-type args. The definition requires interpolation between values,
so it's not clear that there's any point in trying to allow other types.

percentile_disc is also limited to floats and intervals in the spec, but
I see absolutely no reason whatsoever for this, since the definition
given is valid for any type with ordering operators; there is no reason
not to make it fully polymorphic. (The requirement for ordering will be
enforced in parse-analysis anyway, by the ORDER BY transformations, and
the function simply returns one of the input values unaltered.)

--
Andrew (irc:RhodiumToad)

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6Josh Berkus
josh@agliodbs.com
In reply to: Andrew Gierth (#1)
Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

Andrew,

Well, as you probably know, the spec is a whole pile of random
special-case syntax and any similarities are probably more accidental
than anything else.

Hah, I didn't realize that our ordered aggregate syntax even *was* spec.

A major difference is that in agg(x order by y), the values of y are
not passed to the aggregate function - they serve no purpose other
than controlling the order of the "x" values. Whereas in WITHIN GROUP,
the values in the ORDER BY ... clause are in some sense the primary
input to the aggregate, and the "p" argument is secondary and can't
vary between rows of the group.

Our implementation does heavily reuse the existing executor mechanics
for ORDER BY in aggregates, and it also reuses a fair chunk of the
parser code for it, but there are significant differences.

Well, seems like it would work the same as

agg_func(constx,coly,colz ORDER BY coly, colz)

... which means you could reuse a LOT of the internal plumbing. Or am I
missing something?

Also, what would a CREATE AGGREGATE and state function definition for
custom WITHIN GROUP aggregates look like?

Any time you want to calculate what the rank, dense_rank or cume_dist
would be of a specific row within a group without actually adding the
row to the group, this is how it's done.

I don't have any practical examples to hand, but this beast seems to
be implemented in at least Oracle and MSSQL so I guess it has uses.

Well, I still can't imagine a practical use for it, at least based on
RANK. I certainly have no objections if you have the code, though.

I'll also point out that mode() requires ordered input as well, so add
that to the set of functions we'll want to eventually support.

One thing I find myself wanting with ordered aggregates is the ability
to exclude NULLs. Thoughts?

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Andrew Gierth (#1)
Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

Josh Berkus wrote:

Hah, I didn't realize that our ordered aggregate syntax even *was* spec.

The spec defines agg(x order by y) only for array_agg and xmlagg; the
generalization to arbitrary other aggregates is our extension. (But
kind of obvious really.)

Our implementation does heavily reuse the existing executor mechanics
for ORDER BY in aggregates, and it also reuses a fair chunk of the
parser code for it, but there are significant differences.

Well, seems like it would work the same as

agg_func(constx,coly,colz ORDER BY coly, colz)

... which means you could reuse a LOT of the internal plumbing. Or am I
missing something?

You missed the part above which said "...does heavily reuse..." :-)

i.e. we are in fact reusing a lot of the internal plumbing.

Also, what would a CREATE AGGREGATE and state function definition for
custom WITHIN GROUP aggregates look like?

Now this is exactly the part we haven't nailed down yet and want ideas
for.

The problem is, given that the parser is looking at:

foo(p1,p2,...) within group (order by q1,q2,...)

how do we best represent the possible matching functions in pg_proc
and pg_aggregate? Our partial solution so far does not allow
polymorphism to work properly, so we need a better way; I'm hoping for
some independent suggestions before I post my own ideas.

--
Andrew (irc:RhodiumToad)

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Josh Berkus
josh@agliodbs.com
In reply to: Andrew Gierth (#1)
Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

The problem is, given that the parser is looking at:

foo(p1,p2,...) within group (order by q1,q2,...)

how do we best represent the possible matching functions in pg_proc
and pg_aggregate? Our partial solution so far does not allow
polymorphism to work properly, so we need a better way; I'm hoping for
some independent suggestions before I post my own ideas.

Yeah, you'd need to extend VARIADIC somehow. That is, I should be able
to define a function as:

percentile_state (
pctl float,
ordercols VARIADIC ANY )
returns VARIADIC ANY

... so that it can handle the sorting. Another way to look at it would be:

percentile_state (
pctl float,
orderedset ANONYMOUS ROW )
returns ANONYMOUS ROW as ...

... because really, what you're handing the state function is an
anonymous row type constructed of the order by phrase. Of course, then
we have to have some way to manipulate the anonymous row from within the
function; at the very least, an equality operator.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Noah Misch
noah@leadboat.com
In reply to: Andrew Gierth (#7)
Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

On Thu, Jul 18, 2013 at 10:33:15PM +0000, Andrew Gierth wrote:

Josh Berkus wrote:

Well, seems like it would work the same as

agg_func(constx,coly,colz ORDER BY coly, colz)

I'd try transforming WITHIN GROUP into the above during parse analysis. The
default would be the transformation for hypothetical set functions:

agg(x,y,z) WITHIN GROUP (ORDER BY a,b,c) -> agg(x,y,z ORDER BY a,b,c)

Add a CREATE AGGREGATE option, say SQL_INVERSE_DISTRIBUTION_FUNCTION =
{true|false} or SQLIDF, that chooses the IDF transformation:

agg(x) WITHIN GROUP (ORDER BY y) -> agg(x, y ORDER BY y)

Then there's perhaps no new core aggregation or function candidacy machinery.
(I don't know whether VARIADIC transition functions work today, but that would
become an orthogonal project.) Compare how we handle standard interval typmod
syntax; only the parser and deparser know about it.

Atri's description upthread sounded pretty similar to that.

Also, what would a CREATE AGGREGATE and state function definition for
custom WITHIN GROUP aggregates look like?

Now this is exactly the part we haven't nailed down yet and want ideas
for.

PERCENTILE_DISC would be declared as (float8, anyelement) with that SQLIDF
option. To diagnose nonsensical calls made through nonstandard syntax, it
could dig into its AggState to verify that its second argument is equal() to
its first ORDER BY expression.

There would be a question of whether to accept the WITHIN GROUP syntax for any
aggregate or just for those for which the standard indicates it. Then follows
the question of when to deparse as WITHIN GROUP and when to deparse as the
internal syntax. I'd lean toward accepting WITHIN GROUP for any aggregate but
deparsing that way only SQLIDF aggregates and aggregates with names matching
the standard hypothetical set function names. Or you could add a second
CREATE AGGREGATE option requesting hypothetical-set-function deparse style.

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#10Tom Lane
tgl@sss.pgh.pa.us
In reply to: Noah Misch (#9)
Re: Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

Noah Misch <noah@leadboat.com> writes:

(I don't know whether VARIADIC transition functions work today, but that would
become an orthogonal project.)

Coincidentally enough, some Salesforce folk were asking me about allowing
VARIADIC aggregates just a few days ago. I experimented enough to find
out that if you make an array-accepting transition function, and then
force the aggregate's pg_proc entry to look like it's variadic (by
manually setting provariadic and some other fields), then everything
seems to Just Work: the parser and executor are both fine with it.
So I think all that's needed here is to add some syntax support to
CREATE AGGREGATE, and probably make some tweaks in pg_dump. I was
planning to go work on that sometime soon.

Having said that, though, what Andrew seemed to want was VARIADIC ANY,
which is a *completely* different kettle of fish, since the actual
parameters can't be converted to an array. I'm not sure if that's
as easy to support.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Atri Sharma
atri.jiit@gmail.com
In reply to: Tom Lane (#10)
1 attachment(s)
Re: Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

Hi all,

This is our current work-in-progress patch for WITHIN GROUP.

What mostly works:
- percentile_cont(float8) within group (order by float8)
- percentile_cont(float8) within group (order by interval)
- percentile_disc(float8) within group (order by float8)

What doesn't work:
- supporting other types in percentile_disc (want polymorphism to
work first)
- no commands yet to add new ordered set functions (want to nail
down the catalog representation first)
- no hypothetical set functions yet (need to resolve the above two
points first)
- some rough edges
- probably some bugs
- docs

Implementation details:

For execution, we repurpose the existing aggregate-orderby mechanics.
Given func(directargs) WITHIN GROUP (ORDER BY args), we process the
(ORDER BY args) into a tuplesort in the same way currently done for
agg(args ORDER BY args). Rather than using a transfn, we then call the
finalfn as finalfn(directargs), providing an API by which the finalfn
can access the tuplesort. (This is somewhat inspired by the window
function API, but unfortunately has nothing in common with it in terms
of requirements, so we couldn't just reuse it.)

func(p1,p2,...) WITHIN GROUP (ORDER BY q1,q2,...) is represented in the
catalog with two pg_proc rows:

func(p1,p2,...,q1,q2,...) (proisagg=true)
func_final(p1,p2,...)

with the usual pg_aggregate row linking them, though aggtransfn is set
to InvalidOid (as is aggtranstype) and an additional flag indicates
that this is an ordered set function.

(This representation is inadequate for a number of reasons; it does not
handle polymorphism well and would require special-case coding for
hypothetical set functions, which we have not yet tackled. See our other
post.)

Regards,

Atri
--
Regards,

Atri
l'apprenant

Attachments:

patch19713context.patchapplication/octet-stream; name=patch19713context.patchDownload
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
***************
*** 4410,4415 **** ExecInitExpr(Expr *node, PlanState *parent)
--- 4410,4416 ----
  
  					astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
  														 parent);
+ 					astate->orddirectargs = (List *) ExecInitExpr((Expr *) aggref->orddirectargs, parent);
  					astate->aggfilter = ExecInitExpr(aggref->aggfilter,
  													 parent);
  
*** a/src/backend/executor/functions.c
--- b/src/backend/executor/functions.c
***************
*** 381,387 **** sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
  								  list_make1(subfield),
  								  list_make1(param),
  								  NIL, NULL, false, false, false,
! 								  NULL, true, cref->location);
  	}
  
  	return param;
--- 381,387 ----
  								  list_make1(subfield),
  								  list_make1(param),
  								  NIL, NULL, false, false, false,
! 								  false, NULL, true, cref->location);
  	}
  
  	return param;
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
***************
*** 105,110 ****
--- 105,112 ----
   */
  typedef struct AggStatePerAggData
  {
+ 	NodeTag type;
+ 	
  	/*
  	 * These values are set up during ExecInitAgg() and do not change
  	 * thereafter:
***************
*** 114,119 **** typedef struct AggStatePerAggData
--- 116,124 ----
  	AggrefExprState *aggrefstate;
  	Aggref	   *aggref;
  
+ 	/* Pointer to parent AggState node */
+ 	AggState   *aggstate;
+ 
  	/* number of input arguments for aggregate function proper */
  	int			numArguments;
  
***************
*** 126,137 **** typedef struct AggStatePerAggData
  
  	/*
  	 * fmgr lookup data for transfer functions --- only valid when
! 	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
! 	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
  	FmgrInfo	finalfn;
  
  	/* Input collation derived for aggregate */
  	Oid			aggCollation;
  
--- 131,152 ----
  
  	/*
  	 * fmgr lookup data for transfer functions --- only valid when
! 	 * corresponding oid is not InvalidOid.
  	 */
  	FmgrInfo	transfn;
  	FmgrInfo	finalfn;
  
+ 	/*
+ 	 * If >0, aggregate as a whole is strict (skips null input)
+ 	 * The value specifies how many columns to check; normal aggs
+ 	 * only check numArguments, while ordered set functions check
+ 	 * numInputs.
+ 	 *
+ 	 * Ordered set functions have a separate strictness state for
+ 	 * the finalfn, which is tested separately.
+ 	 */
+ 	int         numStrict;
+ 
  	/* Input collation derived for aggregate */
  	Oid			aggCollation;
  
***************
*** 204,209 **** typedef struct AggStatePerAggData
--- 219,227 ----
  	 */
  
  	Tuplesortstate *sortstate;	/* sort object, if DISTINCT or ORDER BY */
+ 
+ 	int64 number_of_rows;		/* number of rows */
+ 
  }	AggStatePerAggData;
  
  /*
***************
*** 300,305 **** initialize_aggregates(AggState *aggstate,
--- 318,325 ----
  		AggStatePerAgg peraggstate = &peragg[aggno];
  		AggStatePerGroup pergroupstate = &pergroup[aggno];
  
+ 		peraggstate->number_of_rows = 0;
+ 
  		/*
  		 * Start a fresh sort operation for each DISTINCT/ORDER BY aggregate.
  		 */
***************
*** 383,396 **** advance_transition_function(AggState *aggstate,
  	MemoryContext oldContext;
  	Datum		newVal;
  	int			i;
  
! 	if (peraggstate->transfn.fn_strict)
  	{
  		/*
  		 * For a strict transfn, nothing happens when there's a NULL input; we
  		 * just keep the prior transValue.
  		 */
! 		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
  				return;
--- 403,419 ----
  	MemoryContext oldContext;
  	Datum		newVal;
  	int			i;
+ 	int         numStrict = peraggstate->numStrict;
  
! 	Assert(OidIsValid(peraggstate->transfn_oid));
! 
! 	if (numStrict > 0)
  	{
  		/*
  		 * For a strict transfn, nothing happens when there's a NULL input; we
  		 * just keep the prior transValue.
  		 */
! 		for (i = 1; i <= numStrict; i++)
  		{
  			if (fcinfo->argnull[i])
  				return;
***************
*** 506,529 **** advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
  
  		if (peraggstate->numSortCols > 0)
  		{
  			/* DISTINCT and/or ORDER BY case */
  			Assert(slot->tts_nvalid == peraggstate->numInputs);
  
  			/*
  			 * If the transfn is strict, we want to check for nullity before
  			 * storing the row in the sorter, to save space if there are a lot
! 			 * of nulls.  Note that we must only check numArguments columns,
! 			 * not numInputs, since nullity in columns used only for sorting
! 			 * is not relevant here.
  			 */
! 			if (peraggstate->transfn.fn_strict)
  			{
! 				for (i = 0; i < nargs; i++)
  				{
  					if (slot->tts_isnull[i])
  						break;
  				}
! 				if (i < nargs)
  					continue;
  			}
  
--- 529,552 ----
  
  		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
  			 * storing the row in the sorter, to save space if there are a lot
! 			 * of nulls.
  			 */
! 			if (numStrict > 0)
  			{
! 				for (i = 0; i < numStrict; i++)
  				{
  					if (slot->tts_isnull[i])
  						break;
  				}
! 				if (i < numStrict)
  					continue;
  			}
  
***************
*** 534,539 **** advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
--- 557,564 ----
  								   slot->tts_isnull[0]);
  			else
  				tuplesort_puttupleslot(peraggstate->sortstate, slot);
+ 
+ 			++peraggstate->number_of_rows;
  		}
  		else
  		{
***************
*** 756,768 **** finalize_aggregate(AggState *aggstate,
  	if (OidIsValid(peraggstate->finalfn_oid))
  	{
  		FunctionCallInfoData fcinfo;
  
! 		InitFunctionCallInfoData(fcinfo, &(peraggstate->finalfn), 1,
! 								 peraggstate->aggCollation,
! 								 (void *) aggstate, NULL);
! 		fcinfo.arg[0] = pergroupstate->transValue;
! 		fcinfo.argnull[0] = pergroupstate->transValueIsNull;
! 		if (fcinfo.flinfo->fn_strict && pergroupstate->transValueIsNull)
  		{
  			/* don't call a strict function with NULL inputs */
  			*resultVal = (Datum) 0;
--- 781,820 ----
  	if (OidIsValid(peraggstate->finalfn_oid))
  	{
  		FunctionCallInfoData fcinfo;
+ 		bool isnull = false;
+ 
+ 		if (!(peraggstate->aggref->isordset))
+ 		{
+ 			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;
+ 
+ 			InitFunctionCallInfoData(fcinfo, &(peraggstate->finalfn), list_length(args),
+ 								 	peraggstate->aggCollation,
+ 								 	(void *) peraggstate, NULL);
+ 
+ 			foreach (lc, args)
+ 			{
+ 				ExprState *expr = (ExprState *) lfirst(lc);
+ 
+ 				fcinfo.arg[i] = ExecEvalExpr(expr, aggstate->ss.ps.ps_ExprContext,  &fcinfo.argnull[i], NULL);
+ 				if (fcinfo.argnull[i])
+ 					isnull = true;
  
! 				++i;
! 			}
! 		}
! 
! 		if (isnull && fcinfo.flinfo->fn_strict)
  		{
  			/* don't call a strict function with NULL inputs */
  			*resultVal = (Datum) 0;
***************
*** 1164,1169 **** agg_retrieve_direct(AggState *aggstate)
--- 1216,1232 ----
  		}
  
  		/*
+ 		 * Use the representative input tuple for any references to
+ 		 * non-aggregated input columns in the qual and tlist.	(If we are not
+ 		 * grouping, and there are no input rows at all, we will come here
+ 		 * with an empty firstSlot ... but if not grouping, there can't be any
+ 		 * references to non-aggregated input columns, so no problem.)
+ 		 * We do this before finalizing because for ordered set functions,
+ 		 * finalize_aggregates can evaluate arguments referencing the tuple.
+ 		 */
+ 		econtext->ecxt_outertuple = firstSlot;
+ 
+ 		/*
  		 * Done scanning input tuple group. Finalize each aggregate
  		 * calculation, and stash results in the per-output-tuple context.
  		 */
***************
*** 1174,1187 **** agg_retrieve_direct(AggState *aggstate)
  
  			if (peraggstate->numSortCols > 0)
  			{
! 				if (peraggstate->numInputs == 1)
! 					process_ordered_aggregate_single(aggstate,
! 													 peraggstate,
! 													 pergroupstate);
! 				else
! 					process_ordered_aggregate_multi(aggstate,
! 													peraggstate,
! 													pergroupstate);
  			}
  
  			finalize_aggregate(aggstate, peraggstate, pergroupstate,
--- 1237,1253 ----
  
  			if (peraggstate->numSortCols > 0)
  			{
! 				if (!(peraggstate->aggref->isordset))
! 				{
! 					if (peraggstate->numInputs == 1)
! 						process_ordered_aggregate_single(aggstate,
! 														 peraggstate,
! 														 pergroupstate);
! 					else
! 						process_ordered_aggregate_multi(aggstate,
! 														peraggstate,
! 														pergroupstate);
! 				}
  			}
  
  			finalize_aggregate(aggstate, peraggstate, pergroupstate,
***************
*** 1189,1203 **** agg_retrieve_direct(AggState *aggstate)
  		}
  
  		/*
- 		 * Use the representative input tuple for any references to
- 		 * non-aggregated input columns in the qual and tlist.	(If we are not
- 		 * grouping, and there are no input rows at all, we will come here
- 		 * with an empty firstSlot ... but if not grouping, there can't be any
- 		 * references to non-aggregated input columns, so no problem.)
- 		 */
- 		econtext->ecxt_outertuple = firstSlot;
- 
- 		/*
  		 * Check the qual (HAVING clause); if the group does not match, ignore
  		 * it and loop back to try to process another group.
  		 */
--- 1255,1260 ----
***************
*** 1580,1585 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
--- 1637,1643 ----
  		Datum		textInitVal;
  		int			i;
  		ListCell   *lc;
+         bool        is_strict;
  
  		/* Planner should have assigned aggregate to correct level */
  		Assert(aggref->agglevelsup == 0);
***************
*** 1601,1606 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
--- 1659,1667 ----
  		/* Nope, so assign a new PerAgg record */
  		peraggstate = &peragg[++aggno];
  
+ 		peraggstate->type = T_AggStatePerAggData;
+ 		peraggstate->aggstate = aggstate;
+ 
  		/* Mark Aggref state node with assigned index in the result array */
  		aggrefstate->aggno = aggno;
  
***************
*** 1644,1668 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  		peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
  		peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
  
! 		/* Check that aggregate owner has permission to call component fns */
  		{
  			HeapTuple	procTuple;
  			Oid			aggOwner;
  
  			procTuple = SearchSysCache1(PROCOID,
  										ObjectIdGetDatum(aggref->aggfnoid));
  			if (!HeapTupleIsValid(procTuple))
  				elog(ERROR, "cache lookup failed for function %u",
  					 aggref->aggfnoid);
! 			aggOwner = ((Form_pg_proc) GETSTRUCT(procTuple))->proowner;
  			ReleaseSysCache(procTuple);
  
! 			aclresult = pg_proc_aclcheck(transfn_oid, aggOwner,
! 										 ACL_EXECUTE);
! 			if (aclresult != ACLCHECK_OK)
  				aclcheck_error(aclresult, ACL_KIND_PROC,
  							   get_func_name(transfn_oid));
! 			InvokeFunctionExecuteHook(transfn_oid);
  			if (OidIsValid(finalfn_oid))
  			{
  				aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
--- 1705,1741 ----
  		peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
  		peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
  
! 		/*
! 		 * Check that aggregate owner has permission to call component fns
! 		 * In passing, fetch the proisstrict flag for the aggregate proper,
! 		 * which subs for the transfn's strictness flag in cases where there
! 		 * is no transfn.
! 		 */
  		{
  			HeapTuple	procTuple;
  			Oid			aggOwner;
+ 			Form_pg_proc procp;
  
  			procTuple = SearchSysCache1(PROCOID,
  										ObjectIdGetDatum(aggref->aggfnoid));
  			if (!HeapTupleIsValid(procTuple))
  				elog(ERROR, "cache lookup failed for function %u",
  					 aggref->aggfnoid);
! 			procp = (Form_pg_proc) GETSTRUCT(procTuple);
! 			aggOwner = procp->proowner;
! 			is_strict = procp->proisstrict;
  			ReleaseSysCache(procTuple);
  
! 			if (OidIsValid(transfn_oid))
! 			{
! 				aclresult = pg_proc_aclcheck(transfn_oid, aggOwner,
! 									         ACL_EXECUTE);
! 				if (aclresult != ACLCHECK_OK && OidIsValid(transfn_oid))
  				aclcheck_error(aclresult, ACL_KIND_PROC,
  							   get_func_name(transfn_oid));
! 				InvokeFunctionExecuteHook(transfn_oid);
! 			}
! 
  			if (OidIsValid(finalfn_oid))
  			{
  				aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
***************
*** 1676,1682 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  
  		/* resolve actual type of transition state, if polymorphic */
  		aggtranstype = aggform->aggtranstype;
! 		if (IsPolymorphicType(aggtranstype))
  		{
  			/* have to fetch the agg's declared input types... */
  			Oid		   *declaredArgTypes;
--- 1749,1755 ----
  
  		/* resolve actual type of transition state, if polymorphic */
  		aggtranstype = aggform->aggtranstype;
! 		if (OidIsValid(aggtranstype) && IsPolymorphicType(aggtranstype))
  		{
  			/* have to fetch the agg's declared input types... */
  			Oid		   *declaredArgTypes;
***************
*** 1704,1711 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  								&transfnexpr,
  								&finalfnexpr);
  
! 		fmgr_info(transfn_oid, &peraggstate->transfn);
! 		fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
  
  		if (OidIsValid(finalfn_oid))
  		{
--- 1777,1789 ----
  								&transfnexpr,
  								&finalfnexpr);
  
! 		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))
  		{
***************
*** 1713,1726 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  			fmgr_info_set_expr((Node *) finalfnexpr, &peraggstate->finalfn);
  		}
  
  		peraggstate->aggCollation = aggref->inputcollid;
  
  		get_typlenbyval(aggref->aggtype,
  						&peraggstate->resulttypeLen,
  						&peraggstate->resulttypeByVal);
! 		get_typlenbyval(aggtranstype,
! 						&peraggstate->transtypeLen,
! 						&peraggstate->transtypeByVal);
  
  		/*
  		 * initval is potentially null, so don't try to access it as a struct
--- 1791,1814 ----
  			fmgr_info_set_expr((Node *) finalfnexpr, &peraggstate->finalfn);
  		}
  
+ 		if (is_strict)
+ 		{
+ 			peraggstate->numStrict = (peraggstate->aggref->isordset ? numInputs : numArguments);
+ 		}
+ 		else
+ 			peraggstate->numStrict = 0;
+ 
  		peraggstate->aggCollation = aggref->inputcollid;
  
  		get_typlenbyval(aggref->aggtype,
  						&peraggstate->resulttypeLen,
  						&peraggstate->resulttypeByVal);
! 		if (OidIsValid(aggtranstype))
! 		{
! 			get_typlenbyval(aggtranstype,
! 							&peraggstate->transtypeLen,
! 							&peraggstate->transtypeByVal);
! 		}
  
  		/*
  		 * initval is potentially null, so don't try to access it as a struct
***************
*** 1743,1749 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  		 * transValue.	This should have been checked at agg definition time,
  		 * but just in case...
  		 */
! 		if (peraggstate->transfn.fn_strict && peraggstate->initValueIsNull)
  		{
  			if (numArguments < 1 ||
  				!IsBinaryCoercible(inputTypes[0], aggtranstype))
--- 1831,1839 ----
  		 * transValue.	This should have been checked at agg definition time,
  		 * but just in case...
  		 */
! 		if (OidIsValid(peraggstate->transfn_oid)
! 			&& peraggstate->transfn.fn_strict
! 			&& peraggstate->initValueIsNull)
  		{
  			if (numArguments < 1 ||
  				!IsBinaryCoercible(inputTypes[0], aggtranstype))
***************
*** 1804,1810 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  			/* If we have only one input, we need its len/byval info. */
  			if (numInputs == 1)
  			{
! 				get_typlenbyval(inputTypes[0],
  								&peraggstate->inputtypeLen,
  								&peraggstate->inputtypeByVal);
  			}
--- 1894,1900 ----
  			/* If we have only one input, we need its len/byval info. */
  			if (numInputs == 1)
  			{
! 				get_typlenbyval(peraggstate->evaldesc->attrs[0]->atttypid,
  								&peraggstate->inputtypeLen,
  								&peraggstate->inputtypeByVal);
  			}
***************
*** 2039,2044 **** AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
--- 2129,2141 ----
  		return AGG_CONTEXT_WINDOW;
  	}
  
+ 	if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ 	{
+ 		if (aggcontext)
+ 			*aggcontext = ((AggStatePerAggData *) fcinfo->context)->aggstate->aggcontext;
+ 		return AGG_CONTEXT_ORDERED;
+ 	}
+ 
  	/* this is just to prevent "uninitialized variable" warnings */
  	if (aggcontext)
  		*aggcontext = NULL;
***************
*** 2062,2064 **** aggregate_dummy(PG_FUNCTION_ARGS)
--- 2159,2209 ----
  		 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;
+ 			*datumtype = peraggstate->evaldesc->attrs[0]->atttypid;
+ 		}
+ 		else
+ 		{
+ 			if (tupdesc)
+ 				*tupdesc = peraggstate->evaldesc;
+ 			*datumtype = InvalidOid;
+ 		}
+ 		
+ 		if (tupslot)
+ 			*tupslot = peraggstate->evalslot;
+ 	}
+ 	else
+ 	{
+ 		elog(ERROR, "AggSetSortInfo called on non ordered set function");
+ 	}
+ }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1138,1145 **** _copyAggref(const Aggref *from)
--- 1138,1147 ----
  	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(isordset);
  	COPY_SCALAR_FIELD(agglevelsup);
  	COPY_LOCATION_FIELD(location);
  
***************
*** 2171,2176 **** _copyFuncCall(const FuncCall *from)
--- 2173,2179 ----
  	COPY_SCALAR_FIELD(agg_star);
  	COPY_SCALAR_FIELD(agg_distinct);
  	COPY_SCALAR_FIELD(func_variadic);
+ 	COPY_SCALAR_FIELD(has_within_group);
  	COPY_NODE_FIELD(over);
  	COPY_LOCATION_FIELD(location);
  
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 196,203 **** _equalAggref(const Aggref *a, const Aggref *b)
--- 196,205 ----
  	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(isordset);
  	COMPARE_SCALAR_FIELD(agglevelsup);
  	COMPARE_LOCATION_FIELD(location);
  
***************
*** 2001,2006 **** _equalFuncCall(const FuncCall *a, const FuncCall *b)
--- 2003,2009 ----
  	COMPARE_SCALAR_FIELD(agg_star);
  	COMPARE_SCALAR_FIELD(agg_distinct);
  	COMPARE_SCALAR_FIELD(func_variadic);
+ 	COMPARE_SCALAR_FIELD(has_within_group);
  	COMPARE_NODE_FIELD(over);
  	COMPARE_LOCATION_FIELD(location);
  
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
***************
*** 530,535 **** makeFuncCall(List *name, List *args, int location)
--- 530,536 ----
  	n->agg_star = FALSE;
  	n->agg_distinct = FALSE;
  	n->func_variadic = FALSE;
+ 	n->has_within_group = FALSE;
  	n->over = NULL;
  	return n;
  }
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 1572,1577 **** expression_tree_walker(Node *node,
--- 1572,1582 ----
  				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;
  			}
***************
*** 2096,2102 **** expression_tree_mutator(Node *node,
--- 2101,2109 ----
  				MUTATE(newnode->args, aggref->args, List *);
  				MUTATE(newnode->aggorder, aggref->aggorder, List *);
  				MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
+ 				MUTATE(newnode->orddirectargs, aggref->orddirectargs, List *);
  				MUTATE(newnode->aggfilter, aggref->aggfilter, Expr *);
+ 
  				return (Node *) newnode;
  			}
  			break;
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 959,966 **** _outAggref(StringInfo str, const Aggref *node)
--- 959,968 ----
  	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(isordset);
  	WRITE_UINT_FIELD(agglevelsup);
  	WRITE_LOCATION_FIELD(location);
  }
***************
*** 2087,2092 **** _outFuncCall(StringInfo str, const FuncCall *node)
--- 2089,2095 ----
  	WRITE_BOOL_FIELD(agg_star);
  	WRITE_BOOL_FIELD(agg_distinct);
  	WRITE_BOOL_FIELD(func_variadic);
+ 	WRITE_BOOL_FIELD(has_within_group);
  	WRITE_NODE_FIELD(over);
  	WRITE_LOCATION_FIELD(location);
  }
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 495,502 **** _readAggref(void)
--- 495,504 ----
  	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(isordset);
  	READ_UINT_FIELD(agglevelsup);
  	READ_LOCATION_FIELD(location);
  
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
***************
*** 486,492 **** count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
  			costs->numOrderedAggs++;
  
  		/* add component function execution costs to appropriate totals */
! 		costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost;
  		if (OidIsValid(aggfinalfn))
  			costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost;
  
--- 486,493 ----
  			costs->numOrderedAggs++;
  
  		/* add component function execution costs to appropriate totals */
! 		if (OidIsValid(aggtransfn))
! 			costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost;
  		if (OidIsValid(aggfinalfn))
  			costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost;
  
***************
*** 516,522 **** count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
  		}
  
  		/* resolve actual type of transition state, if polymorphic */
! 		if (IsPolymorphicType(aggtranstype))
  		{
  			/* have to fetch the agg's declared input types... */
  			Oid		   *declaredArgTypes;
--- 517,523 ----
  		}
  
  		/* resolve actual type of transition state, if polymorphic */
! 		if (OidIsValid(aggtranstype) && IsPolymorphicType(aggtranstype))
  		{
  			/* have to fetch the agg's declared input types... */
  			Oid		   *declaredArgTypes;
***************
*** 539,545 **** count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
  		 * 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;
--- 540,546 ----
  		 * pass-by-reference then we have to add the estimated size of the
  		 * value itself, plus palloc overhead.
  		 */
! 		if (OidIsValid(aggtranstype) && !get_typbyval(aggtranstype))
  		{
  			int32		aggtranstypmod;
  			int32		avgwidth;
***************
*** 3882,3888 **** 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);
  }
  
  /*
--- 3883,3889 ----
  		elog(ERROR, "function's resolved result type changed during planning");
  
  	/* perform any necessary typecasting of arguments */
! 	make_fn_arguments(NULL, args, NULL, actual_arg_types, declared_arg_types);
  }
  
  /*
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 493,498 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
--- 493,499 ----
  %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.
***************
*** 595,601 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
  	VERBOSE VERSION_P VIEW VOLATILE
  
! 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
  
  	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
  	XMLPI XMLROOT XMLSERIALIZE
--- 596,602 ----
  	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
  	VERBOSE VERSION_P VIEW VOLATILE
  
! 	WHEN WHERE WITHIN WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
  
  	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
  	XMLPI XMLROOT XMLSERIALIZE
***************
*** 9413,9418 **** sortby:		a_expr USING qual_all_Op opt_nulls_order
--- 9414,9424 ----
  		;
  
  
+ within_group_clause:
+ 			WITHIN GROUP_P '(' sort_clause ')'  { $$ = $4; }
+ 			| /*EMPTY*/                         { $$ = NULL; }
+          ;
+ 
  select_limit:
  			limit_clause offset_clause			{ $$ = list_make2($2, $1); }
  			| offset_clause limit_clause		{ $$ = list_make2($1, $2); }
***************
*** 11112,11122 **** func_application: func_name '(' ')'
   * (Note that many of the special SQL functions wouldn't actually make any
   * sense as functional index entries, but we ignore that consideration here.)
   */
! func_expr: func_application filter_clause over_clause 
  				{
                		FuncCall *n = (FuncCall*)$1;
! 					n->agg_filter = $2;
! 					n->over = $3;
  					$$ = (Node*)n;
  				} 
  			| func_expr_common_subexpr
--- 11118,11151 ----
   * (Note that many of the special SQL functions wouldn't actually make any
   * sense as functional index entries, but we ignore that consideration here.)
   */
! func_expr: func_application within_group_clause filter_clause over_clause 
  				{
                		FuncCall *n = (FuncCall*)$1;
! 					/*
! 					 * the order clause for WITHIN GROUP and the one
! 					 * for aggregate ORDER BY share a field, so we
! 					 * have to check here that at most one is present.
! 					 * We check for DISTINCT here to give a better
! 					 * error position.  Other consistency checks are
! 					 * deferred to parse_func.c or parse_agg.c
! 					 */
! 					if ($2)
! 					{
! 						if (n->agg_order)
! 							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
***************
*** 12675,12680 **** unreserved_keyword:
--- 12704,12710 ----
  			| VIEW
  			| VOLATILE
  			| WHITESPACE_P
+ 			| WITHIN
  			| WITHOUT
  			| WORK
  			| WRAPPER
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
***************
*** 44,50 **** typedef struct
  	int			sublevels_up;
  } check_ungrouped_columns_context;
  
! static int	check_agg_arguments(ParseState *pstate, List *args, Expr *filter);
  static bool check_agg_arguments_walker(Node *node,
  						   check_agg_arguments_context *context);
  static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
--- 44,50 ----
  	int			sublevels_up;
  } check_ungrouped_columns_context;
  
! static int	check_agg_arguments(ParseState *pstate, List *args, List *agg_ordset, Expr *filter);
  static bool check_agg_arguments_walker(Node *node,
  						   check_agg_arguments_context *context);
  static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
***************
*** 75,81 **** static bool check_ungrouped_columns_walker(Node *node,
   */
  void
  transformAggregateCall(ParseState *pstate, Aggref *agg,
! 					   List *args, List *aggorder, bool agg_distinct)
  {
  	List	   *tlist;
  	List	   *torder;
--- 75,81 ----
   */
  void
  transformAggregateCall(ParseState *pstate, Aggref *agg,
! 					   List *args, List *aggorder, bool agg_distinct, bool agg_within_group)
  {
  	List	   *tlist;
  	List	   *torder;
***************
*** 93,104 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
  	 */
  	tlist = NIL;
  	attno = 1;
! 	foreach(lc, args)
  	{
! 		Expr	   *arg = (Expr *) lfirst(lc);
! 		TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false);
  
! 		tlist = lappend(tlist, tle);
  	}
  
  	/*
--- 93,116 ----
  	 */
  	tlist = NIL;
  	attno = 1;
! 
! 	if (agg_within_group)
! 	{
! 		agg->isordset = TRUE;
! 		agg->orddirectargs = args;
! 	}
! 	else
  	{
! 		foreach(lc, args)
! 		{
! 			Expr	   *arg = (Expr *) lfirst(lc);
! 			TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false);
  
! 			tlist = lappend(tlist, tle);
! 		}
! 
! 		agg->isordset = FALSE;
! 		agg->orddirectargs = NIL;
  	}
  
  	/*
***************
*** 160,166 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
  	 * Check the arguments to compute the aggregate's level and detect
  	 * improper nesting.
  	 */
! 	min_varlevel = check_agg_arguments(pstate, agg->args, agg->aggfilter);
  	agg->agglevelsup = min_varlevel;
  
  	/* Mark the correct pstate level as having aggregates */
--- 172,178 ----
  	 * Check the arguments to compute the aggregate's level and detect
  	 * improper nesting.
  	 */
! 	min_varlevel = check_agg_arguments(pstate, agg->args, agg->orddirectargs, agg->aggfilter);
  	agg->agglevelsup = min_varlevel;
  
  	/* Mark the correct pstate level as having aggregates */
***************
*** 312,318 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
   * which we can't know until we finish scanning the arguments.
   */
  static int
! check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
  {
  	int			agglevel;
  	check_agg_arguments_context context;
--- 324,330 ----
   * which we can't know until we finish scanning the arguments.
   */
  static int
! check_agg_arguments(ParseState *pstate, List *args, List *agg_ordset, Expr *filter)
  {
  	int			agglevel;
  	check_agg_arguments_context context;
***************
*** 330,335 **** check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
--- 342,350 ----
  								  check_agg_arguments_walker,
  								  (void *) &context);
  
+ 	(void) expression_tree_walker((Node *) agg_ordset,	  check_agg_arguments_walker,
+ 								  (void *) &context);
+ 
  	/*
  	 * If we found no vars nor aggs at all, it's a level-zero aggregate;
  	 * otherwise, its level is the minimum of vars or aggs.
***************
*** 353,360 **** check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
  				(errcode(ERRCODE_GROUPING_ERROR),
  				 errmsg("aggregate function calls cannot be nested"),
  				 parser_errposition(pstate,
! 									locate_agg_of_level((Node *) args,
! 														agglevel))));
  
  	return agglevel;
  }
--- 368,375 ----
  				(errcode(ERRCODE_GROUPING_ERROR),
  				 errmsg("aggregate function calls cannot be nested"),
  				 parser_errposition(pstate,
! 								locate_agg_of_level((Node *) args,
! 								agglevel))));
  
  	return agglevel;
  }
***************
*** 823,830 **** check_ungrouped_columns_walker(Node *node,
  	 * We do need to look at aggregates of lower levels, however.
  	 */
  	if (IsA(node, Aggref) &&
! 		(int) ((Aggref *) node)->agglevelsup >= context->sublevels_up)
  		return false;
  
  	/*
  	 * If we have any GROUP BY items that are not simple Vars, check to see if
--- 838,852 ----
  	 * We do need to look at aggregates of lower levels, however.
  	 */
  	if (IsA(node, Aggref) &&
! 		(int) ((Aggref *) node)->agglevelsup > context->sublevels_up)
! 	{
  		return false;
+ 	}
+ 	else if (IsA(node, Aggref) &&
+ 			(int) ((Aggref *) node)->agglevelsup == context->sublevels_up)
+ 	{
+ 		return check_ungrouped_columns_walker(((Aggref *)node)->orddirectargs, context);
+ 	}
  
  	/*
  	 * If we have any GROUP BY items that are not simple Vars, check to see if
***************
*** 1005,1016 **** build_aggregate_fnexprs(Oid *agg_input_types,
  		args = lappend(args, argp);
  	}
  
! 	*transfnexpr = (Expr *) makeFuncExpr(transfn_oid,
! 										 agg_state_type,
! 										 args,
! 										 InvalidOid,
! 										 agg_input_collation,
! 										 COERCE_EXPLICIT_CALL);
  
  	/* see if we have a final function */
  	if (!OidIsValid(finalfn_oid))
--- 1027,1045 ----
  		args = lappend(args, argp);
  	}
  
! 	if (OidIsValid(transfn_oid))
! 	{
! 		*transfnexpr = (Expr *) makeFuncExpr(transfn_oid,
! 										agg_state_type,
! 										args,
! 										InvalidOid,
! 										agg_input_collation,
! 										COERCE_EXPLICIT_CALL);
! 	}
! 	else
! 	{
! 		transfnexpr = NULL;
! 	}
  
  	/* see if we have a final function */
  	if (!OidIsValid(finalfn_oid))
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
***************
*** 464,470 **** transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
  										  list_make1(n),
  										  list_make1(result),
  										  NIL, NULL, false, false, false,
! 										  NULL, true, location);
  			if (newresult == NULL)
  				unknown_attribute(pstate, result, strVal(n), location);
  			result = newresult;
--- 464,470 ----
  										  list_make1(n),
  										  list_make1(result),
  										  NIL, NULL, false, false, false,
! 										  false, NULL, true, location);
  			if (newresult == NULL)
  				unknown_attribute(pstate, result, strVal(n), location);
  			result = newresult;
***************
*** 632,638 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
  											 list_make1(makeString(colname)),
  											 list_make1(node),
  											 NIL, NULL, false, false, false,
! 											 NULL, true, cref->location);
  				}
  				break;
  			}
--- 632,638 ----
  											 list_make1(makeString(colname)),
  											 list_make1(node),
  											 NIL, NULL, false, false, false,
! 											 false, NULL, true, cref->location);
  				}
  				break;
  			}
***************
*** 677,683 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
  											 list_make1(makeString(colname)),
  											 list_make1(node),
  											 NIL, NULL, false, false, false,
! 											 NULL, true, cref->location);
  				}
  				break;
  			}
--- 677,683 ----
  											 list_make1(makeString(colname)),
  											 list_make1(node),
  											 NIL, NULL, false, false, false,
! 											 false, NULL, true, cref->location);
  				}
  				break;
  			}
***************
*** 735,741 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
  											 list_make1(makeString(colname)),
  											 list_make1(node),
  											 NIL, NULL, false, false, false,
! 											 NULL, true, cref->location);
  				}
  				break;
  			}
--- 735,741 ----
  											 list_make1(makeString(colname)),
  											 list_make1(node),
  											 NIL, NULL, false, false, false,
! 											 false, NULL, true, cref->location);
  				}
  				break;
  			}
***************
*** 1248,1255 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
  	targs = NIL;
  	foreach(args, fn->args)
  	{
! 		targs = lappend(targs, transformExprRecurse(pstate,
! 													(Node *) lfirst(args)));
  	}
  
  	/*
--- 1248,1254 ----
  	targs = NIL;
  	foreach(args, fn->args)
  	{
! 		targs = lappend(targs, transformExprRecurse(pstate, (Node *) lfirst(args)));
  	}
  
  	/*
***************
*** 1271,1276 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
--- 1270,1276 ----
  							 fn->agg_star,
  							 fn->agg_distinct,
  							 fn->func_variadic,
+ 							 fn->has_within_group,
  							 fn->over,
  							 false,
  							 fn->location);
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
***************
*** 17,22 ****
--- 17,23 ----
  #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"
***************
*** 27,32 ****
--- 28,34 ----
  #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"
***************
*** 63,69 **** Node *
  ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
  				  List *agg_order, Expr *agg_filter,
  				  bool agg_star, bool agg_distinct, bool func_variadic,
! 				  WindowDef *over, bool is_column, int location)
  {
  	Oid			rettype;
  	Oid			funcid;
--- 65,71 ----
  ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
  				  List *agg_order, Expr *agg_filter,
  				  bool agg_star, bool agg_distinct, bool func_variadic,
! 				  bool agg_within_group, WindowDef *over, bool is_column, int location)
  {
  	Oid			rettype;
  	Oid			funcid;
***************
*** 81,86 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 83,94 ----
  	int			nvargs;
  	Oid			vatype;
  	FuncDetailCode fdresult;
+ 	HeapTuple	tup;
+ 	Oid		aggfinalfn;
+ 	int		number_of_args;
+ 
+ 	/* 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
***************
*** 163,174 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 171,208 ----
  		}
  	}
  
+ 	if(agg_within_group && argnames)
+ 	{
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 			errmsg("Named arguments not allowed in WITHIN GROUP")));
+ 	}
+ 
  	if (fargs)
  	{
  		first_arg = linitial(fargs);
  		Assert(first_arg != NULL);
  	}
  
+ 	/* 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 BY clause.
+ 	 */
+ 	if(agg_within_group)
+ 	{
+ 		Assert(agg_order != NULL);
+ 
+ 		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
***************
*** 247,252 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 281,292 ----
  			errmsg("DISTINCT specified, but %s is not an aggregate function",
  				   NameListToString(funcname)),
  					 parser_errposition(pstate, location)));
+ 		if (agg_within_group)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 			errmsg("WITHIN GROUP specified, but %s is not an ordered set function",
+ 				   NameListToString(funcname)),
+ 					 parser_errposition(pstate, location)));
  		if (agg_order != NIL)
  			ereport(ERROR,
  					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 266,271 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 306,329 ----
  							NameListToString(funcname)),
  					 parser_errposition(pstate, location)));
  	}
+ 	else if (fdresult == FUNCDETAIL_ORDERED)
+ 	{
+ 		if(!agg_within_group)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 					 errmsg("Ordered set function specified, but WITHIN GROUP not present"),
+ 					 parser_errposition(pstate, location)));
+ 		}
+ 		
+ 		if(over)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 					 errmsg("OVER clause in ordered set function not supported"),
+ 					 parser_errposition(pstate, location)));
+ 		}
+ 	}
  	else if (!(fdresult == FUNCDETAIL_AGGREGATE ||
  			   fdresult == FUNCDETAIL_WINDOWFUNC))
  	{
***************
*** 351,357 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
  											   false);
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, fargs, actual_arg_types, declared_arg_types);
  
  	/*
  	 * If it's a variadic function call, transform the last nvargs arguments
--- 409,417 ----
  											   false);
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, fargs, (fdresult==FUNCDETAIL_ORDERED) ? agg_order : NIL, 
! 				actual_arg_types, 
! 				declared_arg_types);
  
  	/*
  	 * If it's a variadic function call, transform the last nvargs arguments
***************
*** 416,425 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
  
  		retval = (Node *) funcexpr;
  	}
! 	else if (fdresult == FUNCDETAIL_AGGREGATE && !over)
  	{
  		/* aggregate function */
  		Aggref	   *aggref = makeNode(Aggref);
  
  		aggref->aggfnoid = funcid;
  		aggref->aggtype = rettype;
--- 476,492 ----
  
  		retval = (Node *) funcexpr;
  	}
! 	else if ((fdresult == FUNCDETAIL_AGGREGATE && !over) || fdresult == FUNCDETAIL_ORDERED)
  	{
  		/* aggregate function */
  		Aggref	   *aggref = makeNode(Aggref);
+ 		if(agg_within_group && fdresult == FUNCDETAIL_AGGREGATE)
+ 		{
+ 			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;
***************
*** 430,435 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 497,522 ----
  		/* agglevelsup will be set by transformAggregateCall */
  		aggref->location = location;
  
+ 		if (fdresult == FUNCDETAIL_ORDERED)
+ 		{
+ 			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);
+ 			aggfinalfn = classForm->aggfinalfn;
+ 			number_of_args = get_func_nargs(aggfinalfn);
+ 			if (number_of_args != list_length(fargs))
+ 			{
+ 				ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 					 errmsg("Number of arguments does not match fargs")));
+ 			}
+ 
+ 			ReleaseSysCache(tup);
+ 		}
+ 
  		/*
  		 * Reject attempt to call a parameterless aggregate without (*)
  		 * syntax.	This is mere pedantry but some folks insisted ...
***************
*** 460,466 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
  					 parser_errposition(pstate, location)));
  
  		/* parse_agg.c does additional aggregate-specific processing */
! 		transformAggregateCall(pstate, aggref, fargs, agg_order, agg_distinct);
  
  		retval = (Node *) aggref;
  	}
--- 547,553 ----
  					 parser_errposition(pstate, location)));
  
  		/* parse_agg.c does additional aggregate-specific processing */
! 		transformAggregateCall(pstate, aggref, fargs, agg_order, agg_distinct, agg_within_group);
  
  		retval = (Node *) aggref;
  	}
***************
*** 469,474 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 556,569 ----
  		/* 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.
  		 */
***************
*** 1264,1269 **** func_get_detail(List *funcname,
--- 1359,1366 ----
  
  		ftup = SearchSysCache1(PROCOID,
  							   ObjectIdGetDatum(best_candidate->oid));
+ 
+ 		
  		if (!HeapTupleIsValid(ftup))	/* should not happen */
  			elog(ERROR, "cache lookup failed for function %u",
  				 best_candidate->oid);
***************
*** 1341,1347 **** func_get_detail(List *funcname,
  			}
  		}
  		if (pform->proisagg)
! 			result = FUNCDETAIL_AGGREGATE;
  		else if (pform->proiswindow)
  			result = FUNCDETAIL_WINDOWFUNC;
  		else
--- 1438,1460 ----
  			}
  		}
  		if (pform->proisagg)
! 		{
! 			Form_pg_aggregate pfagg;
! 			HeapTuple	ftup_agg;
! 
! 			ftup_agg = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(best_candidate->oid));
! 			pfagg = (Form_pg_aggregate) GETSTRUCT(ftup_agg);
! 			if (pfagg->aggisordsetfunc)
! 			{
! 				result = FUNCDETAIL_ORDERED;
! 			}
! 			else
! 			{
! 				result = FUNCDETAIL_AGGREGATE;
! 			}
! 
! 			ReleaseSysCache(ftup_agg);
! 		}
  		else if (pform->proiswindow)
  			result = FUNCDETAIL_WINDOWFUNC;
  		else
***************
*** 1370,1379 **** func_get_detail(List *funcname,
--- 1483,1494 ----
  void
  make_fn_arguments(ParseState *pstate,
  				  List *fargs,
+ 				  List *agg_order,
  				  Oid *actual_arg_types,
  				  Oid *declared_arg_types)
  {
  	ListCell   *current_fargs;
+ 	ListCell   *current_aoargs;
  	int			i = 0;
  
  	foreach(current_fargs, fargs)
***************
*** 1414,1419 **** make_fn_arguments(ParseState *pstate,
--- 1529,1553 ----
  		}
  		i++;
  	}
+ 
+ 	foreach(current_aoargs, agg_order)
+ 	{
+ 		if (actual_arg_types[i] != declared_arg_types[i])
+ 		{
+ 			SortBy	   *node = (SortBy *) lfirst(current_aoargs);
+ 			SortBy     *temp = NULL;
+ 
+ 			temp = coerce_type(pstate,
+ 							node->node,
+ 							actual_arg_types[i],
+ 							declared_arg_types[i], -1,
+ 							COERCION_IMPLICIT,
+ 							COERCE_IMPLICIT_CAST,
+ 							-1);
+ 			node->node = temp;
+ 		}
+ 		i++;
+ 	}
  }
  
  /*
*** a/src/backend/parser/parse_oper.c
--- b/src/backend/parser/parse_oper.c
***************
*** 823,829 **** make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
  											   false);
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
  
  	/* and build the expression node */
  	result = makeNode(OpExpr);
--- 823,829 ----
  											   false);
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types);
  
  	/* and build the expression node */
  	result = makeNode(OpExpr);
***************
*** 953,959 **** make_scalar_array_op(ParseState *pstate, List *opname,
  	declared_arg_types[1] = res_atypeId;
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
  
  	/* and build the expression node */
  	result = makeNode(ScalarArrayOpExpr);
--- 953,959 ----
  	declared_arg_types[1] = res_atypeId;
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types);
  
  	/* and build the expression node */
  	result = makeNode(ScalarArrayOpExpr);
*** a/src/backend/utils/adt/Makefile
--- b/src/backend/utils/adt/Makefile
***************
*** 25,31 **** OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
  	rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \
  	tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \
  	network.o mac.o inet_cidr_ntop.o inet_net_pton.o \
! 	ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \
  	ascii.o quote.o pgstatfuncs.o encode.o dbsize.o genfile.o trigfuncs.o \
  	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
  	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
--- 25,31 ----
  	rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \
  	tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \
  	network.o mac.o inet_cidr_ntop.o inet_net_pton.o \
! 	inversedistribution.o ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \
  	ascii.o quote.o pgstatfuncs.o encode.o dbsize.o genfile.o trigfuncs.o \
  	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
  	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
*** /dev/null
--- b/src/backend/utils/adt/inversedistribution.c
***************
*** 0 ****
--- 1,237 ----
+ /*-------------------------------------------------------------------------
+  *
+  * 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"
+ 
+ /*
+  * percentile_disc(float8)  - discrete (nearest) percentile
+  */
+ 
+ Datum percentile_disc_final(PG_FUNCTION_ARGS);
+ 
+ Datum
+ percentile_disc_final(PG_FUNCTION_ARGS)
+ {
+ 	float8 percentile = PG_GETARG_FLOAT8(0);
+ 	int64 rowcount = AggSetGetRowCount(fcinfo);
+ 	Tuplesortstate *sorter;
+ 	Oid datumtype;
+ 	Datum val;
+ 	bool isnull;
+ 	int64 skiprows;
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ 
+ 	Assert(datumtype == FLOAT8OID);
+ 
+ 	if (percentile < 0 || percentile > 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("percentile value %g is not 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.
+ 	 */
+ 
+ 	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 float8 is by-ref, "val" already points to palloced space in
+ 	 * our memory context. No need to do more.
+ 	 */
+ 
+ 	if (isnull)
+ 		PG_RETURN_NULL();
+ 	else
+ 		PG_RETURN_DATUM(val);
+ }
+ 
+ 
+ 
+ /*
+  * percentile_cont(float8)  - continuous (nearest) percentile
+  */
+ 
+ Datum percentile_cont_final(PG_FUNCTION_ARGS);
+ 
+ Datum
+ percentile_cont_final(PG_FUNCTION_ARGS)
+ {
+ 	float8 percentile = PG_GETARG_FLOAT8(0);
+ 	int64 rowcount = AggSetGetRowCount(fcinfo);
+ 	Tuplesortstate *sorter;
+ 	Oid datumtype;
+ 	Datum val;
+ 	Datum first_row;
+ 	Datum second_row;
+ 	float8 first_row_val;
+ 	float8 second_row_val;
+ 	float8 proportion;
+ 	bool isnull;
+ 	int64 skiprows;
+ 	int64 lower_row = 0;
+ 	int64 higher_row = 0;
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ 
+ 	Assert(datumtype == FLOAT8OID);
+ 
+ 	if (percentile < 0 || percentile > 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("percentile value %g is not 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 (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;
+ 		first_row_val = DatumGetFloat8(first_row);
+ 		second_row_val = DatumGetFloat8(second_row); 
+ 		val = Float8GetDatum(first_row_val + (proportion *(second_row_val - first_row_val)));
+ 	}
+ 
+ 	if (isnull)
+ 		PG_RETURN_NULL();
+ 	else
+ 		PG_RETURN_DATUM(val);
+ }
+ 
+ /*
+  * percentile_interval_cont(Interval)  - continuous (nearest) percentile for Interval
+  */
+ 
+ Datum percentile_interval_cont_final(PG_FUNCTION_ARGS);
+ 
+ Datum
+ percentile_interval_cont_final(PG_FUNCTION_ARGS)
+ {
+ 	float8 percentile = PG_GETARG_FLOAT8(0);
+ 	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;
+ 	Datum diff_result;
+ 	Datum mul_result;
+ 	Datum add_result;
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ 
+ 	Assert(datumtype == INTERVALOID);
+ 
+ 	if (percentile < 0 || percentile > 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("percentile value %g is not 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 (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;
+ 
+ 		diff_result = DirectFunctionCall2(interval_mi, second_row, first_row);
+ 		mul_result = DirectFunctionCall2(interval_mul, diff_result, Float8GetDatumFast(proportion));
+ 		add_result = DirectFunctionCall2(interval_pl, mul_result, first_row);
+ 		val = add_result;
+ 	}
+ 
+ 	if (isnull)
+ 		PG_RETURN_NULL();
+ 	else
+ 		PG_RETURN_DATUM(val);
+ }
+ 
+ 
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 402,407 **** static char *generate_function_name(Oid funcid, int nargs,
--- 402,409 ----
  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 ")
  
***************
*** 7384,7389 **** static void
--- 7386,7468 ----
  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;
+ 
+ 	/* Extract the regular arguments, ignoring resjunk stuff for the moment */
+ 	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 (tle->resjunk)
+ 			continue;
+ 		if (nargs >= FUNC_MAX_ARGS)		/* paranoia */
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ 					 errmsg("too many arguments")));
+ 		argtypes[nargs] = exprType(arg);
+ 		arglist = lappend(arglist, arg);
+ 		nargs++;
+ 	}
+ 
+ 	appendStringInfo(buf, "%s(",
+ 					 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;
***************
*** 7424,7437 **** 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, ')');
  }
  
  /*
--- 7503,7508 ----
***************
*** 7472,7478 **** get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
  	if (wfunc->aggfilter != NULL)
  	{
  		appendStringInfoString(buf, ") FILTER (WHERE ");
! 		get_rule_expr((Node *) wfunc->aggfilter, context, false);
  	}
  
  	appendStringInfoString(buf, ") OVER ");
--- 7543,7549 ----
  	if (wfunc->aggfilter != NULL)
  	{
  		appendStringInfoString(buf, ") FILTER (WHERE ");
! 		get_rule_expr((Node *)wfunc->aggfilter, context, false);
  	}
  
  	appendStringInfoString(buf, ") OVER ");
*** a/src/backend/utils/sort/tuplesort.c
--- b/src/backend/utils/sort/tuplesort.c
***************
*** 1411,1433 **** tuplesort_performsort(Tuplesortstate *state)
   * Internal routine to fetch the next tuple in either forward or back
   * direction into *stup.  Returns FALSE if no more tuples.
   * If *should_free is set, the caller must pfree stup.tuple when done with it.
   */
  static bool
  tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  						  SortTuple *stup, bool *should_free)
  {
  	unsigned int tuplen;
  
  	switch (state->status)
  	{
  		case TSS_SORTEDINMEM:
  			Assert(forward || state->randomAccess);
! 			*should_free = false;
  			if (forward)
  			{
  				if (state->current < state->memtupcount)
  				{
! 					*stup = state->memtuples[state->current++];
  					return true;
  				}
  				state->eof_reached = true;
--- 1411,1439 ----
   * Internal routine to fetch the next tuple in either forward or back
   * direction into *stup.  Returns FALSE if no more tuples.
   * If *should_free is set, the caller must pfree stup.tuple when done with it.
+  * stup may be null to move without fetching.
   */
  static bool
  tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  						  SortTuple *stup, bool *should_free)
  {
  	unsigned int tuplen;
+ 	SortTuple dummy;
+ 	SortTuple *ptup = stup ? stup : &dummy;
  
  	switch (state->status)
  	{
  		case TSS_SORTEDINMEM:
  			Assert(forward || state->randomAccess);
! 			if (should_free)
! 				*should_free = false;
  			if (forward)
  			{
  				if (state->current < state->memtupcount)
  				{
! 					if (stup)
! 						*stup = state->memtuples[state->current];
! 					state->current++;
  					return true;
  				}
  				state->eof_reached = true;
***************
*** 1459,1479 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  					if (state->current <= 0)
  						return false;
  				}
! 				*stup = state->memtuples[state->current - 1];
  				return true;
  			}
  			break;
  
  		case TSS_SORTEDONTAPE:
  			Assert(forward || state->randomAccess);
! 			*should_free = true;
  			if (forward)
  			{
  				if (state->eof_reached)
  					return false;
  				if ((tuplen = getlen(state, state->result_tape, true)) != 0)
  				{
! 					READTUP(state, stup, state->result_tape, tuplen);
  					return true;
  				}
  				else
--- 1465,1489 ----
  					if (state->current <= 0)
  						return false;
  				}
! 				if (stup)
! 					*stup = state->memtuples[state->current - 1];
  				return true;
  			}
  			break;
  
  		case TSS_SORTEDONTAPE:
  			Assert(forward || state->randomAccess);
! 			if (should_free)
! 				*should_free = true;
  			if (forward)
  			{
  				if (state->eof_reached)
  					return false;
  				if ((tuplen = getlen(state, state->result_tape, true)) != 0)
  				{
! 					READTUP(state, ptup, state->result_tape, tuplen);
! 					if (!stup && dummy.tuple)
! 						pfree(dummy.tuple);
  					return true;
  				}
  				else
***************
*** 1546,1557 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  									  state->result_tape,
  									  tuplen))
  				elog(ERROR, "bogus tuple length in backward scan");
! 			READTUP(state, stup, state->result_tape, tuplen);
  			return true;
  
  		case TSS_FINALMERGE:
  			Assert(forward);
! 			*should_free = true;
  
  			/*
  			 * This code should match the inner loop of mergeonerun().
--- 1556,1570 ----
  									  state->result_tape,
  									  tuplen))
  				elog(ERROR, "bogus tuple length in backward scan");
! 			READTUP(state, ptup, state->result_tape, tuplen);
! 			if (!stup && dummy.tuple)
! 				pfree(dummy.tuple);
  			return true;
  
  		case TSS_FINALMERGE:
  			Assert(forward);
! 			if (should_free)
! 				*should_free = true;
  
  			/*
  			 * This code should match the inner loop of mergeonerun().
***************
*** 1563,1573 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  				int			tupIndex;
  				SortTuple  *newtup;
  
! 				*stup = state->memtuples[0];
  				/* returned tuple is no longer counted in our memory space */
! 				if (stup->tuple)
  				{
! 					tuplen = GetMemoryChunkSpace(stup->tuple);
  					state->availMem += tuplen;
  					state->mergeavailmem[srcTape] += tuplen;
  				}
--- 1576,1586 ----
  				int			tupIndex;
  				SortTuple  *newtup;
  
! 				*ptup = state->memtuples[0];
  				/* returned tuple is no longer counted in our memory space */
! 				if (ptup->tuple)
  				{
! 					tuplen = GetMemoryChunkSpace(ptup->tuple);
  					state->availMem += tuplen;
  					state->mergeavailmem[srcTape] += tuplen;
  				}
***************
*** 1598,1603 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
--- 1611,1618 ----
  				newtup->tupindex = state->mergefreelist;
  				state->mergefreelist = tupIndex;
  				state->mergeavailslots[srcTape]++;
+ 				if (!stup && dummy.tuple)
+ 					pfree(dummy.tuple);
  				return true;
  			}
  			return false;
***************
*** 1620,1639 **** tuplesort_gettupleslot(Tuplesortstate *state, bool forward,
  	MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext);
  	SortTuple	stup;
  	bool		should_free;
  
! 	if (!tuplesort_gettuple_common(state, forward, &stup, &should_free))
! 		stup.tuple = NULL;
  
  	MemoryContextSwitchTo(oldcontext);
  
! 	if (stup.tuple)
  	{
! 		ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free);
  		return true;
  	}
  	else
  	{
! 		ExecClearTuple(slot);
  		return false;
  	}
  }
--- 1635,1656 ----
  	MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext);
  	SortTuple	stup;
  	bool		should_free;
+ 	bool        found;
  
! 	found = tuplesort_gettuple_common(state, forward, (slot ? &stup : NULL), &should_free);
  
  	MemoryContextSwitchTo(oldcontext);
  
! 	if (found)
  	{
! 		if (slot)
! 			ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free);
  		return true;
  	}
  	else
  	{
! 		if (slot)
! 			ExecClearTuple(slot);
  		return false;
  	}
  }
***************
*** 1692,1715 **** tuplesort_getdatum(Tuplesortstate *state, bool forward,
  	SortTuple	stup;
  	bool		should_free;
  
! 	if (!tuplesort_gettuple_common(state, forward, &stup, &should_free))
  	{
  		MemoryContextSwitchTo(oldcontext);
  		return false;
  	}
  
! 	if (stup.isnull1 || state->datumTypeByVal)
  	{
! 		*val = stup.datum1;
! 		*isNull = stup.isnull1;
! 	}
! 	else
! 	{
! 		if (should_free)
  			*val = stup.datum1;
  		else
! 			*val = datumCopy(stup.datum1, false, state->datumTypeLen);
! 		*isNull = false;
  	}
  
  	MemoryContextSwitchTo(oldcontext);
--- 1709,1735 ----
  	SortTuple	stup;
  	bool		should_free;
  
! 	if (!tuplesort_gettuple_common(state, forward, (val ? &stup : NULL), &should_free))
  	{
  		MemoryContextSwitchTo(oldcontext);
  		return false;
  	}
  
! 	if (val)
  	{
! 		if (stup.isnull1 || state->datumTypeByVal)
! 		{
  			*val = stup.datum1;
+ 			*isNull = stup.isnull1;
+ 		}
  		else
! 		{
! 			if (should_free)
! 				*val = stup.datum1;
! 			else
! 				*val = datumCopy(stup.datum1, false, state->datumTypeLen);
! 			*isNull = false;
! 		}
  	}
  
  	MemoryContextSwitchTo(oldcontext);
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
***************
*** 44,49 **** CATALOG(pg_aggregate,2600) BKI_WITHOUT_OIDS
--- 44,50 ----
  	regproc		aggfinalfn;
  	Oid			aggsortop;
  	Oid			aggtranstype;
+ 	bool		aggisordsetfunc;
  
  #ifdef CATALOG_VARLEN			/* variable-length fields start here */
  	text		agginitval;
***************
*** 62,74 **** typedef FormData_pg_aggregate *Form_pg_aggregate;
   * ----------------
   */
  
! #define Natts_pg_aggregate				6
  #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
  
  
  /* ----------------
--- 63,76 ----
   * ----------------
   */
  
! #define Natts_pg_aggregate				7
  #define Anum_pg_aggregate_aggfnoid		1
  #define Anum_pg_aggregate_aggtransfn	2
  #define Anum_pg_aggregate_aggfinalfn	3
  #define Anum_pg_aggregate_aggsortop		4
  #define Anum_pg_aggregate_aggtranstype	5
! #define Anum_pg_aggregate_aggisordsetfunc	6
! #define Anum_pg_aggregate_agginitval	7
  
  
  /* ----------------
***************
*** 77,240 **** 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}" ));
  
  /* 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_ ));
  
  /* 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_ ));
  
  /* 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_ ));
  
  /* count */
! DATA(insert ( 2147	int8inc_any		-				0		20		"0" ));
! DATA(insert ( 2803	int8inc			-				0		20		"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}" ));
  
  /* 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}" ));
  
  /* 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}" ));
  
  /* 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}" ));
  
  /* 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}" ));
  
  /* 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}" ));
  
  /* 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}" ));
  
  /* 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_ ));
  
  /* 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_ ));
  
  /* xml */
! DATA(insert ( 2901 xmlconcat2	  -					0	142		_null_ ));
  
  /* array */
! DATA(insert ( 2335	array_agg_transfn	array_agg_finalfn		0	2281	_null_ ));
  
  /* text */
! DATA(insert ( 3538	string_agg_transfn	string_agg_finalfn		0	2281	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	bytea_string_agg_transfn	bytea_string_agg_finalfn		0	2281	_null_ ));
  
  /* json */
! DATA(insert ( 3175	json_agg_transfn	json_agg_finalfn		0	2281	_null_ ));
  
  /*
   * prototypes for functions in pg_aggregate.c
   */
--- 79,246 ----
   */
  
  /* avg */
! DATA(insert ( 2100	int8_avg_accum	numeric_avg		0	1231	f "{0,0}" ));
! DATA(insert ( 2101	int4_avg_accum	int8_avg		0	1016	f "{0,0}" ));
! DATA(insert ( 2102	int2_avg_accum	int8_avg		0	1016	f "{0,0}" ));
! DATA(insert ( 2103	numeric_avg_accum	numeric_avg		0 1231	f "{0,0}" ));
! DATA(insert ( 2104	float4_accum	float8_avg		0	1022	f "{0,0,0}" ));
! DATA(insert ( 2105	float8_accum	float8_avg		0	1022	f "{0,0,0}" ));
! DATA(insert ( 2106	interval_accum	interval_avg	0	1187	f "{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	int8_sum		-				0	1700	f _null_ ));
! DATA(insert ( 2108	int4_sum		-				0	20		f _null_ ));
! DATA(insert ( 2109	int2_sum		-				0	20		f _null_ ));
! DATA(insert ( 2110	float4pl		-				0	700		f _null_ ));
! DATA(insert ( 2111	float8pl		-				0	701		f _null_ ));
! DATA(insert ( 2112	cash_pl			-				0	790		f _null_ ));
! DATA(insert ( 2113	interval_pl		-				0	1186	f _null_ ));
! DATA(insert ( 2114	numeric_add		-				0	1700	f _null_ ));
  
  /* max */
! DATA(insert ( 2115	int8larger		-				413		20		f _null_ ));
! DATA(insert ( 2116	int4larger		-				521		23		f _null_ ));
! DATA(insert ( 2117	int2larger		-				520		21		f _null_ ));
! DATA(insert ( 2118	oidlarger		-				610		26		f _null_ ));
! DATA(insert ( 2119	float4larger	-				623		700		f _null_ ));
! DATA(insert ( 2120	float8larger	-				674		701		f _null_ ));
! DATA(insert ( 2121	int4larger		-				563		702		f _null_ ));
! DATA(insert ( 2122	date_larger		-				1097	1082	f _null_ ));
! DATA(insert ( 2123	time_larger		-				1112	1083	f _null_ ));
! DATA(insert ( 2124	timetz_larger	-				1554	1266	f _null_ ));
! DATA(insert ( 2125	cashlarger		-				903		790		f _null_ ));
! DATA(insert ( 2126	timestamp_larger	-			2064	1114	f _null_ ));
! DATA(insert ( 2127	timestamptz_larger	-			1324	1184	f _null_ ));
! DATA(insert ( 2128	interval_larger -				1334	1186	f _null_ ));
! DATA(insert ( 2129	text_larger		-				666		25		f _null_ ));
! DATA(insert ( 2130	numeric_larger	-				1756	1700	f _null_ ));
! DATA(insert ( 2050	array_larger	-				1073	2277	f _null_ ));
! DATA(insert ( 2244	bpchar_larger	-				1060	1042	f _null_ ));
! DATA(insert ( 2797	tidlarger		-				2800	27		f _null_ ));
! DATA(insert ( 3526	enum_larger		-				3519	3500	f _null_ ));
  
  /* min */
! DATA(insert ( 2131	int8smaller		-				412		20		f _null_ ));
! DATA(insert ( 2132	int4smaller		-				97		23		f _null_ ));
! DATA(insert ( 2133	int2smaller		-				95		21		f _null_ ));
! DATA(insert ( 2134	oidsmaller		-				609		26		f _null_ ));
! DATA(insert ( 2135	float4smaller	-				622		700		f _null_ ));
! DATA(insert ( 2136	float8smaller	-				672		701		f _null_ ));
! DATA(insert ( 2137	int4smaller		-				562		702		f _null_ ));
! DATA(insert ( 2138	date_smaller	-				1095	1082	f _null_ ));
! DATA(insert ( 2139	time_smaller	-				1110	1083	f _null_ ));
! DATA(insert ( 2140	timetz_smaller	-				1552	1266	f _null_ ));
! DATA(insert ( 2141	cashsmaller		-				902		790		f _null_ ));
! DATA(insert ( 2142	timestamp_smaller	-			2062	1114	f _null_ ));
! DATA(insert ( 2143	timestamptz_smaller -			1322	1184	f _null_ ));
! DATA(insert ( 2144	interval_smaller	-			1332	1186	f _null_ ));
! DATA(insert ( 2145	text_smaller	-				664		25		f _null_ ));
! DATA(insert ( 2146	numeric_smaller -				1754	1700	f _null_ ));
! DATA(insert ( 2051	array_smaller	-				1072	2277	f _null_ ));
! DATA(insert ( 2245	bpchar_smaller	-				1058	1042	f _null_ ));
! DATA(insert ( 2798	tidsmaller		-				2799	27		f _null_ ));
! DATA(insert ( 3527	enum_smaller	-				3518	3500	f _null_ ));
  
  /* count */
! DATA(insert ( 2147	int8inc_any		-				0		20		f "0" ));
! DATA(insert ( 2803	int8inc			-				0		20		f "0" ));
  
  /* var_pop */
! DATA(insert ( 2718	int8_accum	numeric_var_pop 0	1231	f "{0,0,0}" ));
! DATA(insert ( 2719	int4_accum	numeric_var_pop 0	1231	f "{0,0,0}" ));
! DATA(insert ( 2720	int2_accum	numeric_var_pop 0	1231	f "{0,0,0}" ));
! DATA(insert ( 2721	float4_accum	float8_var_pop 0	1022	f "{0,0,0}" ));
! DATA(insert ( 2722	float8_accum	float8_var_pop 0	1022	f "{0,0,0}" ));
! DATA(insert ( 2723	numeric_accum  numeric_var_pop 0	1231	f "{0,0,0}" ));
  
  /* var_samp */
! DATA(insert ( 2641	int8_accum	numeric_var_samp	0	1231	f "{0,0,0}" ));
! DATA(insert ( 2642	int4_accum	numeric_var_samp	0	1231	f "{0,0,0}" ));
! DATA(insert ( 2643	int2_accum	numeric_var_samp	0	1231	f "{0,0,0}" ));
! DATA(insert ( 2644	float4_accum	float8_var_samp 0	1022	f "{0,0,0}" ));
! DATA(insert ( 2645	float8_accum	float8_var_samp 0	1022	f "{0,0,0}" ));
! DATA(insert ( 2646	numeric_accum  numeric_var_samp 0	1231	f "{0,0,0}" ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	int8_accum	numeric_var_samp	0	1231	f "{0,0,0}" ));
! DATA(insert ( 2149	int4_accum	numeric_var_samp	0	1231	f "{0,0,0}" ));
! DATA(insert ( 2150	int2_accum	numeric_var_samp	0	1231	f "{0,0,0}" ));
! DATA(insert ( 2151	float4_accum	float8_var_samp 0	1022	f "{0,0,0}" ));
! DATA(insert ( 2152	float8_accum	float8_var_samp 0	1022	f "{0,0,0}" ));
! DATA(insert ( 2153	numeric_accum  numeric_var_samp 0	1231	f "{0,0,0}" ));
  
  /* stddev_pop */
! DATA(insert ( 2724	int8_accum	numeric_stddev_pop		0	1231	f "{0,0,0}" ));
! DATA(insert ( 2725	int4_accum	numeric_stddev_pop		0	1231	f "{0,0,0}" ));
! DATA(insert ( 2726	int2_accum	numeric_stddev_pop		0	1231	f "{0,0,0}" ));
! DATA(insert ( 2727	float4_accum	float8_stddev_pop	0	1022	f "{0,0,0}" ));
! DATA(insert ( 2728	float8_accum	float8_stddev_pop	0	1022	f "{0,0,0}" ));
! DATA(insert ( 2729	numeric_accum	numeric_stddev_pop	0	1231	f "{0,0,0}" ));
  
  /* stddev_samp */
! DATA(insert ( 2712	int8_accum	numeric_stddev_samp		0	1231	f "{0,0,0}" ));
! DATA(insert ( 2713	int4_accum	numeric_stddev_samp		0	1231	f "{0,0,0}" ));
! DATA(insert ( 2714	int2_accum	numeric_stddev_samp		0	1231	f "{0,0,0}" ));
! DATA(insert ( 2715	float4_accum	float8_stddev_samp	0	1022	f "{0,0,0}" ));
! DATA(insert ( 2716	float8_accum	float8_stddev_samp	0	1022	f "{0,0,0}" ));
! DATA(insert ( 2717	numeric_accum	numeric_stddev_samp 0	1231	f "{0,0,0}" ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	int8_accum	numeric_stddev_samp		0	1231	f "{0,0,0}" ));
! DATA(insert ( 2155	int4_accum	numeric_stddev_samp		0	1231	f "{0,0,0}" ));
! DATA(insert ( 2156	int2_accum	numeric_stddev_samp		0	1231	f "{0,0,0}" ));
! DATA(insert ( 2157	float4_accum	float8_stddev_samp	0	1022	f "{0,0,0}" ));
! DATA(insert ( 2158	float8_accum	float8_stddev_samp	0	1022	f "{0,0,0}" ));
! DATA(insert ( 2159	numeric_accum	numeric_stddev_samp 0	1231	f "{0,0,0}" ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	int8inc_float8_float8		-				0	20		f "0" ));
! DATA(insert ( 2819	float8_regr_accum	float8_regr_sxx			0	1022	f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	float8_regr_accum	float8_regr_syy			0	1022	f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	float8_regr_accum	float8_regr_sxy			0	1022	f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	float8_regr_accum	float8_regr_avgx		0	1022	f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	float8_regr_accum	float8_regr_avgy		0	1022	f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	float8_regr_accum	float8_regr_r2			0	1022	f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	float8_regr_accum	float8_regr_slope		0	1022	f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	float8_regr_accum	float8_regr_intercept	0	1022	f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	float8_regr_accum	float8_covar_pop		0	1022	f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	float8_regr_accum	float8_covar_samp		0	1022	f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	float8_regr_accum	float8_corr				0	1022	f "{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	booland_statefunc	-			58	16		f _null_ ));
! DATA(insert ( 2518	boolor_statefunc	-			59	16		f _null_ ));
! DATA(insert ( 2519	booland_statefunc	-			58	16		f _null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236 int2and		  -					0	21		f _null_ ));
! DATA(insert ( 2237 int2or		  -					0	21		f _null_ ));
! DATA(insert ( 2238 int4and		  -					0	23		f _null_ ));
! DATA(insert ( 2239 int4or		  -					0	23		f _null_ ));
! DATA(insert ( 2240 int8and		  -					0	20		f _null_ ));
! DATA(insert ( 2241 int8or		  -					0	20		f _null_ ));
! DATA(insert ( 2242 bitand		  -					0	1560	f _null_ ));
! DATA(insert ( 2243 bitor		  -					0	1560	f _null_ ));
  
  /* xml */
! DATA(insert ( 2901 xmlconcat2	  -					0	142		f _null_ ));
  
  /* array */
! DATA(insert ( 2335	array_agg_transfn	array_agg_finalfn		0	2281	f _null_ ));
  
  /* text */
! DATA(insert ( 3538	string_agg_transfn	string_agg_finalfn		0	2281	f _null_ ));
  
  /* bytea */
! DATA(insert ( 3545	bytea_string_agg_transfn	bytea_string_agg_finalfn		0	2281	f _null_ ));
  
  /* json */
! DATA(insert ( 3175	json_agg_transfn	json_agg_finalfn		0	2281	f _null_ ));
  
+ /* ordered set function */
+ DATA(insert ( 3931 	- 			percentile_disc_final		0 	0 	t _null_));
+ DATA(insert ( 3935 	- 			percentile_cont_final		0 	0 	t _null_));
+ DATA(insert ( 3939 	- 			percentile_interval_cont_final	0 	0 	t _null_));
  /*
   * prototypes for functions in pg_aggregate.c
   */
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4727,4732 **** DESCR("SP-GiST support for quad tree over range");
--- 4727,4752 ----
  /* 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 701 "701 701" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("percentile_disc");
+ 
+ DATA(insert OID = 3932 ( percentile_disc_final	PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 701 "701" _null_ _null_ _null_ _null_ percentile_disc_final _null_ _null_ _null_ ));
+ DESCR("percentile_disc_final");
+ 
+ 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("percentile_cont");
+ 
+ DATA(insert OID = 3936 ( percentile_cont_final	PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 701 "701" _null_ _null_ _null_ _null_ percentile_cont_final _null_ _null_ _null_ ));
+ DESCR("percentile_cont_final");
+ 
+ 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("percentile_interval_cont");
+ 
+ DATA(insert OID = 3940 ( percentile_interval_cont_final	PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1186 "701" _null_ _null_ _null_ _null_ percentile_interval_cont_final _null_ _null_ _null_ ));
+ DESCR("percentile_interval_cont_final");
+ 
  /*
   * Symbolic values for provolatile column: these indicate whether the result
   * of a function is dependent *only* on the values of its explicit arguments,
*** a/src/include/executor/nodeAgg.h
--- b/src/include/executor/nodeAgg.h
***************
*** 25,28 **** extern Size hash_agg_entry_size(int numAggs);
--- 25,31 ----
  
  extern Datum aggregate_dummy(PG_FUNCTION_ARGS);
  
+ extern int64 AggSetGetRowCount(FunctionCallInfo fcinfo);
+ 
+ 
  #endif   /* NODEAGG_H */
*** a/src/include/fmgr.h
--- b/src/include/fmgr.h
***************
*** 647,655 **** extern void **find_rendezvous_variable(const char *varName);
--- 647,668 ----
  /* AggCheckCallContext can return one of the following codes, or 0: */
  #define AGG_CONTEXT_AGGREGATE	1		/* regular aggregate */
  #define AGG_CONTEXT_WINDOW		2		/* window function */
+ #define AGG_CONTEXT_ORDERED		3		/* ordered set function */
  
  extern int AggCheckCallContext(FunctionCallInfo fcinfo,
  					MemoryContext *aggcontext);
+ extern int64 AggSetGetRowCount(FunctionCallInfo fcinfo);
+ 
+ typedef struct Tuplesortstate fmTuplesortstate;
+ typedef struct tupleDesc *fmTupleDesc;
+ typedef struct TupleTableSlot fmTupleTableSlot;
+ 
+ extern void AggSetGetSortInfo(FunctionCallInfo fcinfo,
+ 							  fmTuplesortstate **sortstate,
+ 							  fmTupleDesc *tupdesc,
+ 							  fmTupleTableSlot **tupslot,
+ 							  Oid *datumtype);
+ 
  
  /*
   * We allow plugin modules to hook function entry/exit.  This is intended
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 588,593 **** typedef struct AggrefExprState
--- 588,594 ----
  {
  	ExprState	xprstate;
  	List	   *args;			/* states of argument expressions */
+ 	List	   *orddirectargs;	/* Ordered direct arguments */
  	ExprState  *aggfilter;		/* FILTER expression */
  	int			aggno;			/* ID number for agg within its plan node */
  } AggrefExprState;
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 425,431 **** typedef enum NodeTag
  	T_WindowObjectData,			/* private in nodeWindowAgg.c */
  	T_TIDBitmap,				/* in nodes/tidbitmap.h */
  	T_InlineCodeBlock,			/* in nodes/parsenodes.h */
! 	T_FdwRoutine				/* in foreign/fdwapi.h */
  } NodeTag;
  
  /*
--- 425,432 ----
  	T_WindowObjectData,			/* private in nodeWindowAgg.c */
  	T_TIDBitmap,				/* in nodes/tidbitmap.h */
  	T_InlineCodeBlock,			/* in nodes/parsenodes.h */
! 	T_FdwRoutine,				/* in foreign/fdwapi.h */
! 	T_AggStatePerAggData			/* private in nodeAgg.c */
  } NodeTag;
  
  /*
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 303,308 **** typedef struct FuncCall
--- 303,309 ----
  	bool		agg_star;		/* argument was really '*' */
  	bool		agg_distinct;	/* arguments were labeled DISTINCT */
  	bool		func_variadic;	/* last argument was labeled VARIADIC */
+ 	bool		has_within_group;	/* WITHIN GROUP clause,if any */
  	struct WindowDef *over;		/* OVER clause, if any */
  	int			location;		/* token location, or -1 if unknown */
  } FuncCall;
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 247,254 **** typedef struct Aggref
--- 247,256 ----
  	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		isordset;	/* If node is from an ordered set function */
  	Index		agglevelsup;	/* > 0 if agg belongs to outer query */
  	int			location;		/* token location, or -1 if unknown */
  } Aggref;
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 411,416 **** PG_KEYWORD("where", WHERE, RESERVED_KEYWORD)
--- 411,417 ----
  PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("window", WINDOW, RESERVED_KEYWORD)
  PG_KEYWORD("with", WITH, RESERVED_KEYWORD)
+ PG_KEYWORD("within", WITHIN, UNRESERVED_KEYWORD)
  PG_KEYWORD("without", WITHOUT, UNRESERVED_KEYWORD)
  PG_KEYWORD("work", WORK, UNRESERVED_KEYWORD)
  PG_KEYWORD("wrapper", WRAPPER, UNRESERVED_KEYWORD)
*** a/src/include/parser/parse_agg.h
--- b/src/include/parser/parse_agg.h
***************
*** 17,23 ****
  
  extern void transformAggregateCall(ParseState *pstate, Aggref *agg,
  					   List *args, List *aggorder,
! 					   bool agg_distinct);
  extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
  						WindowDef *windef);
  
--- 17,23 ----
  
  extern void transformAggregateCall(ParseState *pstate, Aggref *agg,
  					   List *args, List *aggorder,
! 					   bool agg_distinct, bool agg_within_group);
  extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
  						WindowDef *windef);
  
*** a/src/include/parser/parse_func.h
--- b/src/include/parser/parse_func.h
***************
*** 38,51 **** typedef enum
  	FUNCDETAIL_NORMAL,			/* found a matching regular function */
  	FUNCDETAIL_AGGREGATE,		/* found a matching aggregate function */
  	FUNCDETAIL_WINDOWFUNC,		/* found a matching window function */
! 	FUNCDETAIL_COERCION			/* it's a type coercion request */
  } FuncDetailCode;
  
- 
  extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
  				  List *agg_order, Expr *agg_filter,
  				  bool agg_star, bool agg_distinct, bool func_variadic,
! 				  WindowDef *over, bool is_column, int location);
  
  extern FuncDetailCode func_get_detail(List *funcname,
  				List *fargs, List *fargnames,
--- 38,51 ----
  	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_ORDERED		/* ordered function */
  } 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,
! 				  bool agg_within_group, WindowDef *over, bool is_column, int location);
  
  extern FuncDetailCode func_get_detail(List *funcname,
  				List *fargs, List *fargnames,
***************
*** 66,71 **** extern FuncCandidateList func_select_candidate(int nargs,
--- 66,72 ----
  
  extern void make_fn_arguments(ParseState *pstate,
  				  List *fargs,
+ 				  List *agg_order,
  				  Oid *actual_arg_types,
  				  Oid *declared_arg_types);
  
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
***************
*** 1249,1251 **** select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
--- 1249,1307 ----
   {"(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:  Ordered set function specified, but WITHIN GROUP not present
+ 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)
+ 
*** a/src/test/regress/expected/opr_sanity.out
--- b/src/test/regress/expected/opr_sanity.out
***************
*** 700,708 **** SELECT * FROM funcdescs
  
  -- **************** pg_aggregate ****************
  -- Look for illegal values in pg_aggregate fields.
  SELECT ctid, aggfnoid::oid
  FROM pg_aggregate as p1
! WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0;
   ctid | aggfnoid 
  ------+----------
  (0 rows)
--- 700,716 ----
  
  -- **************** pg_aggregate ****************
  -- Look for illegal values in pg_aggregate fields.
+ -- ordered set functions can't have transfns, and must
+ -- have finalfns, but may or may not have transtypes.
+ -- other aggs must have transfns and transtypes with
+ -- optional finalfns.
  SELECT ctid, aggfnoid::oid
  FROM pg_aggregate as p1
! WHERE aggfnoid = 0
!    OR CASE WHEN aggisordsetfunc
!            THEN aggtransfn <> 0 OR aggfinalfn = 0
!            ELSE aggtransfn = 0 OR aggtranstype = 0
!       END;
   ctid | aggfnoid 
  ------+----------
  (0 rows)
***************
*** 764,771 **** WHERE a.aggfnoid = p.oid AND
      a.aggfinalfn = pfn.oid AND
      (pfn.proretset
       OR NOT binary_coercible(pfn.prorettype, p.prorettype)
!      OR pfn.pronargs != 1
!      OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]));
   aggfnoid | proname | oid | proname 
  ----------+---------+-----+---------
  (0 rows)
--- 772,780 ----
      a.aggfinalfn = pfn.oid AND
      (pfn.proretset
       OR NOT binary_coercible(pfn.prorettype, p.prorettype)
!      OR (aggisordsetfunc IS FALSE
!          AND (pfn.pronargs != 1
!               OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]))));
   aggfnoid | proname | oid | proname 
  ----------+---------+-----+---------
  (0 rows)
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
***************
*** 480,482 **** select sum(unique1) FILTER (WHERE
--- 480,501 ----
  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;
+ 
*** a/src/test/regress/sql/opr_sanity.sql
--- b/src/test/regress/sql/opr_sanity.sql
***************
*** 564,573 **** SELECT * FROM funcdescs
  -- **************** pg_aggregate ****************
  
  -- Look for illegal values in pg_aggregate fields.
  
  SELECT ctid, aggfnoid::oid
  FROM pg_aggregate as p1
! WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0;
  
  -- Make sure the matching pg_proc entry is sensible, too.
  
--- 564,581 ----
  -- **************** pg_aggregate ****************
  
  -- Look for illegal values in pg_aggregate fields.
+ -- ordered set functions can't have transfns, and must
+ -- have finalfns, but may or may not have transtypes.
+ -- other aggs must have transfns and transtypes with
+ -- optional finalfns.
  
  SELECT ctid, aggfnoid::oid
  FROM pg_aggregate as p1
! WHERE aggfnoid = 0
!    OR CASE WHEN aggisordsetfunc
!            THEN aggtransfn <> 0 OR aggfinalfn = 0
!            ELSE aggtransfn = 0 OR aggtranstype = 0
!       END;
  
  -- Make sure the matching pg_proc entry is sensible, too.
  
***************
*** 618,625 **** WHERE a.aggfnoid = p.oid AND
      a.aggfinalfn = pfn.oid AND
      (pfn.proretset
       OR NOT binary_coercible(pfn.prorettype, p.prorettype)
!      OR pfn.pronargs != 1
!      OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]));
  
  -- If transfn is strict then either initval should be non-NULL, or
  -- input type should match transtype so that the first non-null input
--- 626,634 ----
      a.aggfinalfn = pfn.oid AND
      (pfn.proretset
       OR NOT binary_coercible(pfn.prorettype, p.prorettype)
!      OR (aggisordsetfunc IS FALSE
!          AND (pfn.pronargs != 1
!               OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]))));
  
  -- If transfn is strict then either initval should be non-NULL, or
  -- input type should match transtype so that the first non-null input
#12John Galloway
jgalloway@salesforce.com
In reply to: Tom Lane (#10)
Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

"some Salesforce folks" that would be me! It looks like I didn't quite
communicate to Tom just what I was looking for as I do indeed want to have
a variable number of "any" types, as:

CREATE AGGREGATE FOO ( ANYELEMENT, <more types>, VARIADIC "any") (
...
STYPE = ANYARRAY
...)
so the corresponding transition function would be
CREATE FUNCTION FOO_sfunc( ANYARRAY, ANYELEMENT, <more types>, VARIADIC
"any") RETURNS ANYARRAY
and the final func is
CREATE FUNCTION FOO_ffunc( ANYARRAY ) RETURNS ANYELEMENT

The functions are in C, and I cheat and actually use the ANYARRAY
transition variable as a struct just keeping the varlena length correct
(thanks to Tom for that idea). Currently I just support a fixed number of
"any" args but really need to have that be variable.

So supporting VARIADIC "any" for user defined aggregates would be most
useful.

On Thu, Jul 18, 2013 at 7:09 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Show quoted text

Noah Misch <noah@leadboat.com> writes:

(I don't know whether VARIADIC transition functions work today, but that

would

become an orthogonal project.)

Coincidentally enough, some Salesforce folk were asking me about allowing
VARIADIC aggregates just a few days ago. I experimented enough to find
out that if you make an array-accepting transition function, and then
force the aggregate's pg_proc entry to look like it's variadic (by
manually setting provariadic and some other fields), then everything
seems to Just Work: the parser and executor are both fine with it.
So I think all that's needed here is to add some syntax support to
CREATE AGGREGATE, and probably make some tweaks in pg_dump. I was
planning to go work on that sometime soon.

Having said that, though, what Andrew seemed to want was VARIADIC ANY,
which is a *completely* different kettle of fish, since the actual
parameters can't be converted to an array. I'm not sure if that's
as easy to support.

regards, tom lane

#13Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Andrew Gierth (#1)
Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

Ok, since Atri posted our work-so-far and there's not been much
comment, I'll outline here my proposed plan of attack.

Rather than, as in the WIP patch, using the agg finalfn to validate
the split between normal args and ORDER BY args, I propose this:

Firstly, as in the WIP patch,

func(a) within group (order by b)

is looked up as though it were func(a,b). The result must be marked as
an ordered set function. A new pg_aggregate integer column,
aggordnargs (?), must be equal to the number of normal args (i.e. (a)
in this case). Note that this may be 0; one can see a legitimate use
case for mode() within group (order by anyelement) for example.

The finalfn must be defined to have the same signature, so its args
are processed as if it were func_final(a,b) - but only a dummy arg is
passed for b. (Similar to the case for window functions.) Resolution
of polymorphic parameters and result types therefore works as normal.

For hypothetical set functions we add a special case, aggordnargs=-1,
for which both the aggregate and the finalfn must be defined as
(variadic "any") and parse analysis detects this case and unifies the
types of the normal args with those of the ORDER BY args.

I propose this new syntax:

create aggregate func(argtypes...) within group (argtypes...) (
[ STYPE = ... , ]
[ SORTOP = ... , ]
[ INITCOND = ... , ]
FINALFUNC = func_final
);

Ordered set functions will typically not need STYPE etc., but
hypothetical set functions will be declared as, e.g.:

create aggregate rank(variadic "any") within group (variadic "any") (
STYPE = boolean,
INITCOND = 'f',
SORTOP = >,
FINALFUNC = rank_hypothetical_final
);

(I'm open to comment as to whether to simply overload the aggsortop
column in pg_aggregate or add a new one. I'm inclined to do the latter.)

The idea here is that a column of type STYPE will be appended to the
list of columns to be sorted, using SORTOP as sort operator, and all
input rows will have this column initialized to the INITCOND value.
This is to make it easy to implement rank() and friends by simply
inserting the hypothetical row into the sort, with a true value to
flag it, and finding its position in the sort result. (Better that
than comparing the hypothetical row against the whole group.)

(Security caveat: it will be necessary for the finalfn in such cases
to validate that the additional column exists with the right
type. Producing the wrong result is acceptable if the values in it are
unexpected; crashing is not.)

Any comment before we get back to coding?

--
Andrew (irc:RhodiumToad)

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#14Noah Misch
noah@leadboat.com
In reply to: Andrew Gierth (#13)
Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

On Tue, Jul 23, 2013 at 01:21:52AM +0000, Andrew Gierth wrote:

For hypothetical set functions we add a special case, aggordnargs=-1,
for which both the aggregate and the finalfn must be defined as
(variadic "any") and parse analysis detects this case and unifies the
types of the normal args with those of the ORDER BY args.

Other aggregates based on this syntax might not desire such type unification.
Having parse analysis do that distorts the character of an "any" argument. I
think the proper place for such processing is the first call to a transition
function. The transition functions could certainly call a new API exposed
under src/backend/parser to do the heavy lifting. But let's not make the
parser presume that an aggordnargs=-1 aggregate always wants its "any"
arguments handled in the manner of the standard hypothetical set functions.

The rest of the plan looks good so far.

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#15Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Andrew Gierth (#1)
Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

Noah Misch said:

Other aggregates based on this syntax might not desire such type unification.

Then there would have to be some way to distinguish that. Maybe those could
have -1 and the standard hypothetical set functions -2, with some flag in
CREATE AGGREGATE to sort it out.

Having parse analysis do that distorts the character of an "any" argument. I
think the proper place for such processing is the first call to a transition
function.

Except there isn't one.

But let's not make the
parser presume that an aggordnargs=-1 aggregate always wants its "any"
arguments handled in the manner of the standard hypothetical set functions.

This has to happen in the parser because these are errors that should be
caught before execution:

rank(foo) within group (order by bar,baz)
rank(integercol) within group (order by textcol)

And collations have to be resolved (pairwise) before sorting can happen:

rank(textval COLLATE "C") within group (order by foo) -- sorts in "C"
rank(textval COLLATE "C") within group (order by bar COLLATE "en_US") -- error

(basically, in rank(x) within group (order by y) where x and y are
collatable, the collation rules apply exactly as though you were doing
(x < y), with all the implicit vs. explicit stuff included)

And this:

rank(1.1) within group (order by intcol)

should become rank(1.1) within group (order by intcol::numeric)

--
Andrew (irc:RhodiumToad)

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#16Noah Misch
noah@leadboat.com
In reply to: Andrew Gierth (#15)
Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

On Wed, Jul 24, 2013 at 04:16:28AM +0000, Andrew Gierth wrote:

Noah Misch said:

Other aggregates based on this syntax might not desire such type unification.

Then there would have to be some way to distinguish that. Maybe those could
have -1 and the standard hypothetical set functions -2, with some flag in
CREATE AGGREGATE to sort it out.

Sure.

But let's not make the
parser presume that an aggordnargs=-1 aggregate always wants its "any"
arguments handled in the manner of the standard hypothetical set functions.

This has to happen in the parser because these are errors that should be
caught before execution:

rank(foo) within group (order by bar,baz)
rank(integercol) within group (order by textcol)

Would be nice to have, but not an overriding concern.

And collations have to be resolved (pairwise) before sorting can happen:

rank(textval COLLATE "C") within group (order by foo) -- sorts in "C"
rank(textval COLLATE "C") within group (order by bar COLLATE "en_US") -- error

(basically, in rank(x) within group (order by y) where x and y are
collatable, the collation rules apply exactly as though you were doing
(x < y), with all the implicit vs. explicit stuff included)

This, though, makes direct parser support nigh inevitable.

The issue to resolve here is whether and to what extent we should implement
the SQL-standard hypothetical set functions as special cases of some
more-generic concept. Here's the declaration you proposed for the rank() HSF:

create aggregate rank(variadic "any") within group (variadic "any") (

This would be the first place to my knowledge where "any" doesn't mean
unrestricted type acceptance at the parser level. If we need bespoke syntax
declaring a function as an HSF with the entailed call behavior nuances, fine.
Treating a declaration with this particular mix of "any" as a secret handshake
requesting those nuances is not the way to go.

STYPE = boolean,
INITCOND = 'f',
SORTOP = >,
FINALFUNC = rank_hypothetical_final
);

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#17Peter Eisentraut
peter_e@gmx.net
In reply to: Atri Sharma (#11)
Re: Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

On Fri, 2013-07-19 at 21:29 +0530, Atri Sharma wrote:

Hi all,

This is our current work-in-progress patch for WITHIN GROUP.

Please fix these compiler warnings:

parse_agg.c: In function ‘check_ungrouped_columns_walker’:
parse_agg.c:848:3: warning: passing argument 1 of ‘check_ungrouped_columns_walker’ from incompatible pointer type [enabled by default]
parse_agg.c:822:1: note: expected ‘struct Node *’ but argument is of type ‘struct List *’

parse_func.c: In function ‘make_fn_arguments’:
parse_func.c:1540:9: warning: assignment from incompatible pointer type [enabled by default]
parse_func.c:1547:15: warning: assignment from incompatible pointer type [enabled by default]

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#18Peter Eisentraut
peter_e@gmx.net
In reply to: Atri Sharma (#11)
Re: Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

On 7/19/13 11:59 AM, Atri Sharma wrote:

Hi all,

This is our current work-in-progress patch for WITHIN GROUP.

This patch needs to be rebased.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#19Atri Sharma
atri.jiit@gmail.com
In reply to: Peter Eisentraut (#18)
Re: Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

Sent from my iPad

On 04-Sep-2013, at 21:38, Peter Eisentraut <peter_e@gmx.net> wrote:

On 7/19/13 11:59 AM, Atri Sharma wrote:

Hi all,

This is our current work-in-progress patch for WITHIN GROUP.

This patch needs to be rebased.

This version of patch is quite old.We will be sending an updated patch before the start of September commitfest, with all the points you mentioned taken care of.

Thanks for the points.

Regards,

Atri

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#20Atri Sharma
atri.jiit@gmail.com
In reply to: Atri Sharma (#19)
1 attachment(s)
Re: Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

On Wed, Sep 4, 2013 at 10:01 PM, Atri Sharma <atri.jiit@gmail.com> wrote:

Sent from my iPad

On 04-Sep-2013, at 21:38, Peter Eisentraut <peter_e@gmx.net> wrote:

On 7/19/13 11:59 AM, Atri Sharma wrote:

Hi all,

This is our current work-in-progress patch for WITHIN GROUP.

This patch needs to be rebased.

Hi All,

This is our complete patch for implementation of WITHIN GROUP.

Functions supported:

percentile_disc
percentile_cont for float8 and intervals
percentile_disc and percentile_cont support arrays of percentiles as well.
mode
rank
dense_rank
percent_rank
cume_dist

The patch also adds support for user defined ordered set functions
with CREATE AGGREGATE.

Polymorphism is now supported, with the original gripes about it now
solved. Essentially, we have added
a new field in pg_aggregate, aggordnargs, which we use it to verify,
having looked up the function, that it is being called correctly.
aggordnargs holds the number of direct args to the aggregate.

Hypothetical set functions build over the extension of VARIADIC, and
all of the hypothetical set functions
have variadic 'any' as their parameter types.

Need review:

1) psql /df and /dfa output.
2) Handling of non hypothetical collations.
3) Need of mode(), and the name.

Feedback/Comments?

Regards,

Atri

Attachments:

withingrouppatchcontextv1.patchapplication/octet-stream; name=withingrouppatchcontextv1.patchDownload
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 352,358 ****
        <entry><structfield>aggtransfn</structfield></entry>
        <entry><type>regproc</type></entry>
        <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
!       <entry>Transition function</entry>
       </row>
       <row>
        <entry><structfield>aggfinalfn</structfield></entry>
--- 352,358 ----
        <entry><structfield>aggtransfn</structfield></entry>
        <entry><type>regproc</type></entry>
        <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
!       <entry>Transition function (zero if none)</entry>
       </row>
       <row>
        <entry><structfield>aggfinalfn</structfield></entry>
***************
*** 370,376 ****
        <entry><structfield>aggtranstype</structfield></entry>
        <entry><type>oid</type></entry>
        <entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
!       <entry>Data type of the aggregate function's internal transition (state) data</entry>
       </row>
       <row>
        <entry><structfield>agginitval</structfield></entry>
--- 370,394 ----
        <entry><structfield>aggtranstype</structfield></entry>
        <entry><type>oid</type></entry>
        <entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
!       <entry>Data type of the aggregate function's internal transition (state) data (zero if none)</entry>
!      </row>
!      <row>
!       <entry><structfield>aggtranssortop</structfield></entry>
!       <entry><type>oid</type></entry>
!       <entry><literal><link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.oid</literal></entry>
!       <entry>An optional sort operator for the type "aggtranstype", used for some kinds of ordered set functions</entry>
!      </row>
!      <row>
!       <entry><structfield>aggordnargs</structfield></entry>
!       <entry><type>int4</type></entry>
!       <entry></entry>
!       <entry>Number of direct arguments to ordered set function; -2 for hypothetical set functions; -1 for ordinary aggregates.</entry>
!      </row>
!      <row>
!       <entry><structfield>aggisordsetfunc</structfield></entry>
!       <entry><type>bool</type></entry>
!       <entry></entry>
!       <entry>A flag to represent whether a function is ordered set or not</entry>
       </row>
       <row>
        <entry><structfield>agginitval</structfield></entry>
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 12197,12202 **** SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab;
--- 12197,12453 ----
  
   </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>
  
*** a/doc/src/sgml/ref/alter_aggregate.sgml
--- b/doc/src/sgml/ref/alter_aggregate.sgml
***************
*** 21,32 **** PostgreSQL documentation
  
   <refsynopsisdiv>
  <synopsis>
! ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
    RENAME TO <replaceable>new_name</replaceable>
  ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
    OWNER TO <replaceable>new_owner</replaceable>
  ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
    SET SCHEMA <replaceable>new_schema</replaceable>
  </synopsis>
   </refsynopsisdiv>
  
--- 21,37 ----
  
   <refsynopsisdiv>
  <synopsis>
! ALTER AGGREGATE
    RENAME TO <replaceable>new_name</replaceable>
  ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
    OWNER TO <replaceable>new_owner</replaceable>
  ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
    SET SCHEMA <replaceable>new_schema</replaceable>
+ 
+ <phrase>where <replaceable>aggregate_signature</replaceable> is one of:</phrase>
+ 
+ <replaceable>name</replaceable> ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
+ <replaceable>name</replaceable> ( [ [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] ] ) WITHIN GROUP ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
  </synopsis>
   </refsynopsisdiv>
  
***************
*** 148,157 **** ALTER AGGREGATE myavg(integer) OWNER TO joe;
    </para>
  
    <para>
!    To move the aggregate function <literal>myavg</literal> for type
!    <type>integer</type> into schema <literal>myschema</literal>:
  <programlisting>
! ALTER AGGREGATE myavg(integer) SET SCHEMA myschema;
  </programlisting></para>
   </refsect1>
  
--- 153,163 ----
    </para>
  
    <para>
!    To move the ordered set function <literal>mypercentile</literal> with
!    direct argument of type <type>float8</type> taking groups
!    of <type>integer</type> type into schema <literal>myschema</literal>:
  <programlisting>
! ALTER AGGREGATE mypercentile(float8) WITHIN GROUP (integer) SET SCHEMA myschema;
  </programlisting></para>
   </refsect1>
  
*** a/doc/src/sgml/ref/alter_extension.sgml
--- b/doc/src/sgml/ref/alter_extension.sgml
***************
*** 30,36 **** ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> DROP <replacea
  
  <phrase>where <replaceable class="PARAMETER">member_object</replaceable> is:</phrase>
  
!   AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) |
    CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
    COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
    CONVERSION <replaceable class="PARAMETER">object_name</replaceable> |
--- 30,36 ----
  
  <phrase>where <replaceable class="PARAMETER">member_object</replaceable> is:</phrase>
  
!   AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) [ WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) ] |
    CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
    COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
    CONVERSION <replaceable class="PARAMETER">object_name</replaceable> |
*** a/doc/src/sgml/ref/comment.sgml
--- b/doc/src/sgml/ref/comment.sgml
***************
*** 23,29 **** PostgreSQL documentation
  <synopsis>
  COMMENT ON
  {
!   AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) |
    CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
    COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
    COLUMN <replaceable class="PARAMETER">relation_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
--- 23,29 ----
  <synopsis>
  COMMENT ON
  {
!   AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) [ WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) ] |
    CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
    COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
    COLUMN <replaceable class="PARAMETER">relation_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
*** a/doc/src/sgml/ref/create_aggregate.sgml
--- b/doc/src/sgml/ref/create_aggregate.sgml
***************
*** 29,34 **** CREATE AGGREGATE <replaceable class="parameter">name</replaceable> ( [ <replacea
--- 29,43 ----
      [ , 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,76 **** CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
    </para>
  
    <para>
!    An aggregate function is made from one or two ordinary
     functions:
     a state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
--- 79,85 ----
    </para>
  
    <para>
!    An ordinary aggregate function is made from one or two ordinary
     functions:
     a state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
***************
*** 165,170 **** SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
--- 174,187 ----
    </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,283 **** SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
--- 295,305 ----
        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,310 **** SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
--- 327,365 ----
       </para>
      </listitem>
     </varlistentry>
+ 
+    <varlistentry>
+     <term><replaceable class="PARAMETER">state_sort_operator</replaceable></term>
+     <listitem>
+      <para>
+       For ordered set functions only, this is a sort operator that can be
+       applied to
+       the <replaceable class="PARAMETER">state_data_type</replaceable>.
+       This is just an operator name (possibly schema-qualified).
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>STRICT</literal></term>
+     <listitem>
+      <para>
+       For ordered set functions only, this flag specifies that the function is
+       strict, i.e. that grouped rows containing nulls are skipped.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>HYPOTHETICAL</literal></term>
+     <listitem>
+      <para>
+       For ordered set functions only, this flag specifies that the aggregate
+       parameters are to be processed according to the requirements for
+       hypothetical set functions.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
  
    <para>
*** a/doc/src/sgml/ref/drop_aggregate.sgml
--- b/doc/src/sgml/ref/drop_aggregate.sgml
***************
*** 21,29 **** PostgreSQL documentation
  
   <refsynopsisdiv>
  <synopsis>
! DROP AGGREGATE [ IF EXISTS ]
!   <replaceable class="parameter">name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">arg_name</replaceable> ] <replaceable class="parameter">arg_data_type</replaceable> [ , ... ] )
    [ CASCADE | RESTRICT ]
  </synopsis>
   </refsynopsisdiv>
  
--- 21,33 ----
  
   <refsynopsisdiv>
  <synopsis>
! DROP AGGREGATE [ IF EXISTS ] <replaceable class="parameter">aggregate_signature</replaceable>
    [ CASCADE | RESTRICT ]
+ 
+ <phrase>where <replaceable>aggregate_signature</replaceable> is one of:</phrase>
+ 
+ <replaceable>name</replaceable> ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
+ <replaceable>name</replaceable> ( [ [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] ] ) WITHIN GROUP ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
  </synopsis>
   </refsynopsisdiv>
  
*** a/doc/src/sgml/ref/security_label.sgml
--- b/doc/src/sgml/ref/security_label.sgml
***************
*** 25,31 **** SECURITY LABEL [ FOR <replaceable class="PARAMETER">provider</replaceable> ] ON
  {
    TABLE <replaceable class="PARAMETER">object_name</replaceable> |
    COLUMN <replaceable class="PARAMETER">table_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
!   AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) |
    DATABASE <replaceable class="PARAMETER">object_name</replaceable> |
    DOMAIN <replaceable class="PARAMETER">object_name</replaceable> |
    EVENT TRIGGER <replaceable class="PARAMETER">object_name</replaceable> |
--- 25,31 ----
  {
    TABLE <replaceable class="PARAMETER">object_name</replaceable> |
    COLUMN <replaceable class="PARAMETER">table_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
!   AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) [ WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) ] |
    DATABASE <replaceable class="PARAMETER">object_name</replaceable> |
    DOMAIN <replaceable class="PARAMETER">object_name</replaceable> |
    EVENT TRIGGER <replaceable class="PARAMETER">object_name</replaceable> |
*** a/doc/src/sgml/syntax.sgml
--- b/doc/src/sgml/syntax.sgml
***************
*** 1706,1711 **** SELECT string_agg(a ORDER BY a, ',') FROM table;  -- incorrect
--- 1706,1759 ----
     </para>
    </sect2>
  
+   <sect2 id="syntax-orderedset">
+    <title>Ordered Set Functions</title>
+ 
+    <indexterm zone="syntax-orderedset">
+     <primary>ordered set function</primary>
+    </indexterm>
+ 
+    <indexterm zone="syntax-orderedset">
+     <primary>aggregate function</primary>
+     <secondary>ordered set function</secondary>
+    </indexterm>
+ 
+    <indexterm zone="syntax-orderedset">
+     <primary>WITHIN GROUP</primary>
+    </indexterm>
+ 
+    <para>
+     An <firstterm>ordered set function</firstterm> is a particular kind of
+     aggregate function which is applied to sorted groups of values and returns
+     a single result for each group which may be influenced by the sort
+     order. Like all aggregate functions, it reduces multiple inputs to a
+     single output value; typical ordered set functions return a percentile
+     extracted from the ordered group, or the rank a specified value would have
+     within that group.  The syntax of an ordered set function is:
+ 
+ <synopsis>
+ <replaceable>function_name</replaceable> ( [ <replaceable>expression</replaceable> [ , ... ] ] ) WITHIN GROUP ( <replaceable>order_by_clause</replaceable> ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+ </synopsis>
+ 
+     where <replaceable>function_name</replaceable> is a previously
+     defined ordered set function (possibly qualified with a schema name) and
+     <replaceable>expression</replaceable> is any value expression that does
+     not itself contain an aggregate expression, a window function call, or any
+     reference to ungrouped columns of the source data.  The
+     mandatory <replaceable>order_by_clause</replaceable> has the same syntax
+     as for a query-level <literal>ORDER BY</> clause, as described
+     in <xref linkend="queries-order">, except that its expressions are always
+     just expressions and cannot be output-column names or numbers.  The
+     expressions of the <replaceable>order_by_clause</replaceable> may, and
+     almost invariably do, refer to the ungrouped columns of the input; the
+     clause defines the grouped input to the function. The optional
+     <replaceable>filter_clause</replaceable> is identical to that for
+     aggregate functions (see <xref linkend="syntax-aggregates">, and is applied
+     to input rows prior to the sort operation.
+    </para>
+ 
+   </sect2>
+ 
    <sect2 id="syntax-window-functions">
     <title>Window Function Calls</title>
  
*** a/doc/src/sgml/xaggr.sgml
--- b/doc/src/sgml/xaggr.sgml
***************
*** 9,28 ****
    </indexterm>
  
    <para>
!    Aggregate functions  in <productname>PostgreSQL</productname>
!    are expressed in terms of <firstterm>state values</firstterm>
!    and <firstterm>state transition functions</firstterm>.
!    That is, an aggregate operates using a state value that is updated
!    as each successive input row is processed.
!    To define a new aggregate
!    function, one selects a data type for the state value,
!    an initial value for the state, and a state transition
!    function.  The state transition function is just an
!    ordinary function that could also be used outside the
!    context of the aggregate.  A <firstterm>final function</firstterm>
!    can also be specified, in case the desired result of the aggregate
!    is different from the data that needs to be kept in the running
!    state value.
    </para>
  
    <para>
--- 9,25 ----
    </indexterm>
  
    <para>
!    Aggregate functions (other than ordered set functions)
!    in <productname>PostgreSQL</productname> are expressed in terms
!    of <firstterm>state values</firstterm> and <firstterm>state transition
!    functions</firstterm>.  That is, an aggregate operates using a state value
!    that is updated as each successive input row is processed.  To define a new
!    aggregate function, one selects a data type for the state value, an initial
!    value for the state, and a state transition function.  The state transition
!    function is just an ordinary function that could also be used outside the
!    context of the aggregate.  A <firstterm>final function</firstterm> can also
!    be specified, in case the desired result of the aggregate is different from
!    the data that needs to be kept in the running state value.
    </para>
  
    <para>
*** a/src/backend/catalog/pg_aggregate.c
--- b/src/backend/catalog/pg_aggregate.c
***************
*** 46,51 **** Oid
--- 46,52 ----
  AggregateCreate(const char *aggName,
  				Oid aggNamespace,
  				int numArgs,
+ 				int numDirectArgs,
  				oidvector *parameterTypes,
  				Datum allParameterTypes,
  				Datum parameterModes,
***************
*** 54,77 **** AggregateCreate(const char *aggName,
  				List *aggtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
! 				const char *agginitval)
  {
  	Relation	aggdesc;
  	HeapTuple	tup;
  	bool		nulls[Natts_pg_aggregate];
  	Datum		values[Natts_pg_aggregate];
  	Form_pg_proc proc;
! 	Oid			transfn;
  	Oid			finalfn = InvalidOid;	/* can be omitted */
  	Oid			sortop = InvalidOid;	/* can be omitted */
  	Oid		   *aggArgTypes = parameterTypes->values;
  	bool		hasPolyArg;
  	bool		hasInternalArg;
  	Oid			rettype;
  	Oid			finaltype;
! 	Oid		   *fnArgs;
! 	int			nargs_transfn;
  	Oid			procOid;
  	TupleDesc	tupDesc;
  	int			i;
--- 55,83 ----
  				List *aggtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
+ 				List *aggtranssortopName,
  				Oid aggTransType,
! 				const char *agginitval,
! 				bool isStrict,
! 				bool isOrderedSet,
! 				bool isHypotheticalSet)
  {
  	Relation	aggdesc;
  	HeapTuple	tup;
  	bool		nulls[Natts_pg_aggregate];
  	Datum		values[Natts_pg_aggregate];
  	Form_pg_proc proc;
! 	Oid			transfn = InvalidOid;	/* can be omitted */
  	Oid			finalfn = InvalidOid;	/* can be omitted */
  	Oid			sortop = InvalidOid;	/* can be omitted */
+ 	Oid			transsortop = InvalidOid;  /* Can be omitted */
  	Oid		   *aggArgTypes = parameterTypes->values;
  	bool		hasPolyArg;
  	bool		hasInternalArg;
+ 	Oid         variadic_type = InvalidOid;
  	Oid			rettype;
  	Oid			finaltype;
! 	Oid		   *fnArgs = palloc((numArgs + 1) * sizeof(Oid));
  	Oid			procOid;
  	TupleDesc	tupDesc;
  	int			i;
***************
*** 83,90 **** AggregateCreate(const char *aggName,
  	if (!aggName)
  		elog(ERROR, "no aggregate name supplied");
  
! 	if (!aggtransfnName)
! 		elog(ERROR, "aggregate must have a transition function");
  
  	/* check for polymorphic and INTERNAL arguments */
  	hasPolyArg = false;
--- 89,108 ----
  	if (!aggName)
  		elog(ERROR, "no aggregate name supplied");
  
! 	if (isOrderedSet)
! 	{
! 		if (aggtransfnName)
! 			elog(ERROR, "Ordered set functions cannot have transition functions");
! 		if (!aggfinalfnName)
! 			elog(ERROR, "Ordered set functions must have final functions");
! 	}
! 	else
! 	{
! 		if (!aggtransfnName)
! 			elog(ERROR, "aggregate must have a transition function");
! 		if (isStrict)
! 			elog(ERROR, "aggregate with transition function must not be explicitly STRICT");
! 	}
  
  	/* check for polymorphic and INTERNAL arguments */
  	hasPolyArg = false;
***************
*** 97,102 **** AggregateCreate(const char *aggName,
--- 115,250 ----
  			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,159 **** AggregateCreate(const char *aggName,
  				 errmsg("cannot determine transition data type"),
  				 errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument.")));
  
! 	/* find the transfn */
! 	nargs_transfn = numArgs + 1;
! 	fnArgs = (Oid *) palloc(nargs_transfn * sizeof(Oid));
! 	fnArgs[0] = aggTransType;
! 	memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
! 	transfn = lookup_agg_function(aggtransfnName, nargs_transfn, fnArgs,
! 								  &rettype);
  
! 	/*
! 	 * Return type of transfn (possibly after refinement by
! 	 * enforce_generic_type_consistency, if transtype isn't polymorphic) must
! 	 * exactly match declared transtype.
! 	 *
! 	 * In the non-polymorphic-transtype case, it might be okay to allow a
! 	 * rettype that's binary-coercible to transtype, but I'm not quite
! 	 * convinced that it's either safe or useful.  When transtype is
! 	 * polymorphic we *must* demand exact equality.
! 	 */
! 	if (rettype != aggTransType)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_DATATYPE_MISMATCH),
! 				 errmsg("return type of transition function %s is not %s",
! 						NameListToString(aggtransfnName),
! 						format_type_be(aggTransType))));
  
! 	tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn));
! 	if (!HeapTupleIsValid(tup))
! 		elog(ERROR, "cache lookup failed for function %u", transfn);
! 	proc = (Form_pg_proc) GETSTRUCT(tup);
  
! 	/*
! 	 * If the transfn is strict and the initval is NULL, make sure first input
! 	 * type and transtype are the same (or at least binary-compatible), so
! 	 * that it's OK to use the first input value as the initial transValue.
! 	 */
! 	if (proc->proisstrict && agginitval == NULL)
! 	{
! 		if (numArgs < 1 ||
! 			!IsBinaryCoercible(aggArgTypes[0], aggTransType))
  			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
  	}
- 	ReleaseSysCache(tup);
  
  	/* handle finalfn, if supplied */
! 	if (aggfinalfnName)
  	{
  		fnArgs[0] = aggTransType;
  		finalfn = lookup_agg_function(aggfinalfnName, 1, fnArgs,
--- 255,340 ----
  				 errmsg("cannot determine transition data type"),
  				 errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument.")));
  
! 	if (!isOrderedSet)
! 	{
! 		/* find the transfn */		
  
! 		fnArgs[0] = aggTransType;
! 		memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
  
! 		transfn = lookup_agg_function(aggtransfnName, numArgs + 1, fnArgs,
! 									  &rettype);
  
! 		/*
! 		 * Return type of transfn (possibly after refinement by
! 		 * enforce_generic_type_consistency, if transtype isn't polymorphic)
! 		 * must exactly match declared transtype.
! 		 *
! 		 * In the non-polymorphic-transtype case, it might be okay to allow a
! 		 * rettype that's binary-coercible to transtype, but I'm not quite
! 		 * convinced that it's either safe or useful.  When transtype is
! 		 * polymorphic we *must* demand exact equality.
! 		 */
! 		if (rettype != aggTransType)
  			ereport(ERROR,
! 					(errcode(ERRCODE_DATATYPE_MISMATCH),
! 					 errmsg("return type of transition function %s is not %s",
! 							NameListToString(aggtransfnName),
! 							format_type_be(aggTransType))));
! 
! 		tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn));
! 		if (!HeapTupleIsValid(tup))
! 			elog(ERROR, "cache lookup failed for function %u", transfn);
! 		proc = (Form_pg_proc) GETSTRUCT(tup);
! 
! 		/*
! 	 	 * If the transfn is strict and the initval is NULL, make sure first
! 	 	 * input type and transtype are the same (or at least
! 	 	 * binary-compatible), so that it's OK to use the first input value as
! 	 	 * the initial transValue.
! 	 	 */
! 		if (proc->proisstrict && agginitval == NULL)
! 		{
! 			if (numArgs < 1 ||
! 				!IsBinaryCoercible(aggArgTypes[0], aggTransType))
! 				ereport(ERROR,
! 						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 						 errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
! 		}
! 		ReleaseSysCache(tup);
  	}
  
  	/* handle finalfn, if supplied */
! 	if (isOrderedSet)
! 	{
! 		int num_final_args = numArgs;
! 
! 		memcpy(fnArgs, aggArgTypes, num_final_args * sizeof(Oid));
! 
! 		/*
! 		 * If there's a transtype, it becomes the last arg to the finalfn;
! 		 * but if the agg (and hence the finalfn) is variadic "any", then
! 		 * this contributes nothing to the signature.
! 		 */
! 		if (aggTransType != InvalidOid && variadic_type != ANYOID)
! 			fnArgs[num_final_args++] = aggTransType;
! 
! 		finalfn = lookup_agg_function(aggfinalfnName, num_final_args, fnArgs,
! 									  &finaltype);
! 
! 		/*
! 		 * this is also checked at runtime for security reasons, but check
! 		 * here too to provide a friendly error (the requirement is because
! 		 * the finalfn will be passed null dummy args for type resolution
! 		 * purposes)
! 		 */
! 
! 		if (func_strict(finalfn))
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("ordered set final functions must not be declared STRICT")));
! 	}
! 	else if (aggfinalfnName)
  	{
  		fnArgs[0] = aggTransType;
  		finalfn = lookup_agg_function(aggfinalfnName, 1, fnArgs,
***************
*** 166,171 **** AggregateCreate(const char *aggName,
--- 347,353 ----
  		 */
  		finaltype = aggTransType;
  	}
+ 
  	Assert(OidIsValid(finaltype));
  
  	/*
***************
*** 207,212 **** AggregateCreate(const char *aggName,
--- 389,406 ----
  								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,231 **** AggregateCreate(const char *aggName,
  			aclcheck_error_type(aclresult, aggArgTypes[i]);
  	}
  
! 	aclresult = pg_type_aclcheck(aggTransType, GetUserId(), ACL_USAGE);
! 	if (aclresult != ACLCHECK_OK)
! 		aclcheck_error_type(aclresult, aggTransType);
  
  	aclresult = pg_type_aclcheck(finaltype, GetUserId(), ACL_USAGE);
  	if (aclresult != ACLCHECK_OK)
  		aclcheck_error_type(aclresult, finaltype);
  
- 
  	/*
  	 * Everything looks okay.  Try to create the pg_proc entry for the
  	 * aggregate.  (This could fail if there's already a conflicting entry.)
--- 411,427 ----
  			aclcheck_error_type(aclresult, aggArgTypes[i]);
  	}
  
! 	if (OidIsValid(aggTransType))
! 	{
! 		aclresult = pg_type_aclcheck(aggTransType, GetUserId(), ACL_USAGE);
! 		if (aclresult != ACLCHECK_OK)
! 			aclcheck_error_type(aclresult, aggTransType);
! 	}
  
  	aclresult = pg_type_aclcheck(finaltype, GetUserId(), ACL_USAGE);
  	if (aclresult != ACLCHECK_OK)
  		aclcheck_error_type(aclresult, finaltype);
  
  	/*
  	 * Everything looks okay.  Try to create the pg_proc entry for the
  	 * aggregate.  (This could fail if there's already a conflicting entry.)
***************
*** 246,252 **** AggregateCreate(const char *aggName,
  							  false,	/* security invoker (currently not
  										 * definable for agg) */
  							  false,	/* isLeakProof */
! 							  false,	/* isStrict (not needed for agg) */
  							  PROVOLATILE_IMMUTABLE,	/* volatility (not
  														 * needed for agg) */
  							  parameterTypes,	/* paramTypes */
--- 442,448 ----
  							  false,	/* security invoker (currently not
  										 * definable for agg) */
  							  false,	/* isLeakProof */
! 							  isStrict,	/* isStrict (needed for ordered set funcs) */
  							  PROVOLATILE_IMMUTABLE,	/* volatility (not
  														 * needed for agg) */
  							  parameterTypes,	/* paramTypes */
***************
*** 272,278 **** AggregateCreate(const char *aggName,
--- 468,478 ----
  	values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
  	values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
  	values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
+ 	values[Anum_pg_aggregate_aggtranssortop - 1] = ObjectIdGetDatum(transsortop);
  	values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
+ 	values[Anum_pg_aggregate_aggordnargs - 1] = Int32GetDatum(numDirectArgs);
+ 	values[Anum_pg_aggregate_aggisordsetfunc - 1] = BoolGetDatum(isOrderedSet);
+ 
  	if (agginitval)
  		values[Anum_pg_aggregate_agginitval - 1] = CStringGetTextDatum(agginitval);
  	else
***************
*** 290,307 **** AggregateCreate(const char *aggName,
  
  	/*
  	 * Create dependencies for the aggregate (above and beyond those already
! 	 * made by ProcedureCreate).  Note: we don't need an explicit dependency
! 	 * on aggTransType since we depend on it indirectly through transfn.
  	 */
  	myself.classId = ProcedureRelationId;
  	myself.objectId = procOid;
  	myself.objectSubId = 0;
  
  	/* Depends on transition function */
! 	referenced.classId = ProcedureRelationId;
! 	referenced.objectId = transfn;
! 	referenced.objectSubId = 0;
! 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  
  	/* Depends on final function, if any */
  	if (OidIsValid(finalfn))
--- 490,512 ----
  
  	/*
  	 * Create dependencies for the aggregate (above and beyond those already
! 	 * made by ProcedureCreate).  Normal aggs don't need an explicit
! 	 * dependency on aggTransType since we depend on it indirectly through
! 	 * transfn, but ordered set functions with variadic "any" do need one
! 	 * (ordered set functions without variadic depend on it via the finalfn).
  	 */
  	myself.classId = ProcedureRelationId;
  	myself.objectId = procOid;
  	myself.objectSubId = 0;
  
  	/* Depends on transition function */
! 	if (OidIsValid(transfn))
! 	{
! 		referenced.classId = ProcedureRelationId;
! 		referenced.objectId = transfn;
! 		referenced.objectSubId = 0;
! 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
! 	}
  
  	/* Depends on final function, if any */
  	if (OidIsValid(finalfn))
***************
*** 321,326 **** AggregateCreate(const char *aggName,
--- 526,549 ----
  		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  	}
  
+ 	/* Depends on transsort operator, if any */
+ 	if (OidIsValid(transsortop))
+ 	{
+ 		referenced.classId = OperatorRelationId;
+ 		referenced.objectId = transsortop;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
+ 	/* May depend on aggTransType if any */
+ 	if (OidIsValid(aggTransType) && isOrderedSet && variadic_type == ANYOID)
+ 	{
+ 		referenced.classId = TypeRelationId;
+ 		referenced.objectId = aggTransType;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	return procOid;
  }
  
*** a/src/backend/commands/aggregatecmds.c
--- b/src/backend/commands/aggregatecmds.c
***************
*** 44,52 ****
   *	DefineAggregate
   *
   * "oldstyle" signals the old (pre-8.2) style where the aggregate input type
!  * is specified by a BASETYPE element in the parameters.  Otherwise,
!  * "args" is a list of FunctionParameter structs defining the agg's arguments.
!  * "parameters" is a list of DefElem representing the agg's definition clauses.
   */
  Oid
  DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
--- 44,55 ----
   *	DefineAggregate
   *
   * "oldstyle" signals the old (pre-8.2) style where the aggregate input type
!  * is specified by a BASETYPE element in the parameters.  Otherwise, "args" is
!  * a pair, whose first element is a list of FunctionParameter structs defining
!  * the agg's arguments (both direct and ordered), and whose second element is
!  * an Integer node with the number of direct args, or -1 if this isn't an
!  * ordered set func.  "parameters" is a list of DefElem representing the agg's
!  * definition clauses.
   */
  Oid
  DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
***************
*** 58,75 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  	List	   *transfuncName = NIL;
  	List	   *finalfuncName = NIL;
  	List	   *sortoperatorName = NIL;
  	TypeName   *baseType = NULL;
  	TypeName   *transType = NULL;
  	char	   *initval = NULL;
  	int			numArgs;
  	oidvector  *parameterTypes;
  	ArrayType  *allParameterTypes;
  	ArrayType  *parameterModes;
  	ArrayType  *parameterNames;
  	List	   *parameterDefaults;
- 	Oid			transTypeId;
  	char		transTypeType;
  	ListCell   *pl;
  
  	/* Convert list of names to a name and namespace */
  	aggNamespace = QualifiedNameGetCreationNamespace(name, &aggName);
--- 61,83 ----
  	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;
  	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,85 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
--- 88,101 ----
  		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,111 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
--- 122,133 ----
  			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,129 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  							defel->defname)));
  	}
  
! 	/*
! 	 * make sure we have our required definitions
! 	 */
! 	if (transType == NULL)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 				 errmsg("aggregate stype must be specified")));
! 	if (transfuncName == NIL)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 				 errmsg("aggregate sfunc must be specified")));
  
  	/*
  	 * look up the aggregate's input datatype(s).
--- 135,169 ----
  							defel->defname)));
  	}
  
! 	if (!isOrderedSet)
! 	{
! 		/*
! 		 * make sure we have our required definitions
! 		 */
! 		if (transType == NULL)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 						errmsg("aggregate stype must be specified")));
! 		if (transfuncName == NIL)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("aggregate sfunc must be specified")));
! 		if (isStrict)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("aggregate with sfunc must not be explicitly declared STRICT")));
! 	}
! 	else
! 	{
! 		if (transfuncName != NIL)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("sfunc must not be specified for ordered set functions")));
! 		if (finalfuncName == NIL)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("finalfunc must be specified for ordered set functions")));
! 	}
  
  	/*
  	 * look up the aggregate's input datatype(s).
***************
*** 173,180 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  					 errmsg("basetype is redundant with aggregate input type specification")));
  
! 		numArgs = list_length(args);
! 		interpret_function_parameter_list(args,
  										  InvalidOid,
  										  true, /* is an aggregate */
  										  queryString,
--- 213,227 ----
  					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  					 errmsg("basetype is redundant with aggregate input type specification")));
  
! 		/*
! 		 * The grammar has already concatenated the direct and ordered
! 		 * args (if any) for us. Note that error checking for position
! 		 * and number of VARIADIC args is not done for us, we have to
! 		 * do it ourselves later (in AggregateCreate)
! 		 */
! 
! 		numArgs = list_length(linitial(args));
! 		interpret_function_parameter_list(linitial(args),
  										  InvalidOid,
  										  true, /* is an aggregate */
  										  queryString,
***************
*** 191,197 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  	}
  
  	/*
! 	 * look up the aggregate's transtype.
  	 *
  	 * transtype can't be a pseudo-type, since we need to be able to store
  	 * values of the transtype.  However, we can allow polymorphic transtype
--- 238,244 ----
  	}
  
  	/*
! 	 * 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,218 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  	 * worse) by connecting up incompatible internal-using functions in an
  	 * aggregate.
  	 */
! 	transTypeId = typenameTypeId(NULL, transType);
! 	transTypeType = get_typtype(transTypeId);
! 	if (transTypeType == TYPTYPE_PSEUDO &&
! 		!IsPolymorphicType(transTypeId))
  	{
! 		if (transTypeId == INTERNALOID && superuser())
! 			 /* okay */ ;
! 		else
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("aggregate transition data type cannot be %s",
! 							format_type_be(transTypeId))));
  	}
  
  	/*
--- 248,267 ----
  	 * worse) by connecting up incompatible internal-using functions in an
  	 * aggregate.
  	 */
! 	if (transType)
  	{
! 		transTypeId = typenameTypeId(NULL, transType);
! 		transTypeType = get_typtype(transTypeId);
! 		if (transTypeType == TYPTYPE_PSEUDO &&
! 			!IsPolymorphicType(transTypeId))
! 		{
! 			if (transTypeId != INTERNALOID || !superuser() || isOrderedSet)
! 				ereport(ERROR,
! 						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 						 errmsg("aggregate transition data type cannot be %s",
! 								format_type_be(transTypeId))));
! 		}
! 
  	}
  
  	/*
***************
*** 224,236 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  	 * value.  However, if it's an incorrect value it seems much more
  	 * user-friendly to complain at CREATE AGGREGATE time.
  	 */
! 	if (initval && transTypeType != TYPTYPE_PSEUDO)
  	{
! 		Oid			typinput,
! 					typioparam;
  
! 		getTypeInputInfo(transTypeId, &typinput, &typioparam);
! 		(void) OidInputFunctionCall(typinput, initval, typioparam, -1);
  	}
  
  	/*
--- 273,295 ----
  	 * value.  However, if it's an incorrect value it seems much more
  	 * user-friendly to complain at CREATE AGGREGATE time.
  	 */
! 	if (transType)
  	{
! 		if (initval && transTypeType != TYPTYPE_PSEUDO)
! 		{
! 			Oid			typinput,
! 						typioparam;
  
! 			getTypeInputInfo(transTypeId, &typinput, &typioparam);
! 			(void) OidInputFunctionCall(typinput, initval, typioparam, -1);
! 		}
! 	}
! 	else
! 	{
! 		if (initval)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					errmsg("INITVAL must not be specified without STYPE")));
  	}
  
  	/*
***************
*** 239,244 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
--- 298,304 ----
  	return AggregateCreate(aggName,		/* aggregate name */
  						   aggNamespace,		/* namespace */
  						   numArgs,
+ 						   numDirectArgs,
  						   parameterTypes,
  						   PointerGetDatum(allParameterTypes),
  						   PointerGetDatum(parameterModes),
***************
*** 247,252 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  						   transfuncName,		/* step function name */
  						   finalfuncName,		/* final function name */
  						   sortoperatorName,	/* sort operator name */
  						   transTypeId, /* transition data type */
! 						   initval);	/* initial condition */
  }
--- 307,316 ----
  						   transfuncName,		/* step function name */
  						   finalfuncName,		/* final function name */
  						   sortoperatorName,	/* sort operator name */
+ 						   transsortoperatorName,  /* transsort operator name */
  						   transTypeId, /* transition data type */
! 						   initval,  /* initial condition */
! 						   isStrict,  /* is explicitly STRICT */
! 						   isOrderedSet,  /* If the function is an ordered set */
! 						   ishypothetical);  /* If the function is a hypothetical set */
  }
*** a/src/backend/commands/functioncmds.c
--- b/src/backend/commands/functioncmds.c
***************
*** 274,281 **** interpret_function_parameter_list(List *parameters,
  		/* handle input parameters */
  		if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
  		{
! 			/* other input parameters can't follow a VARIADIC parameter */
! 			if (varCount > 0)
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  						 errmsg("VARIADIC parameter must be the last input parameter")));
--- 274,286 ----
  		/* handle input parameters */
  		if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
  		{
! 			/*
! 			 * For functions, other input parameters can't follow a VARIADIC
! 			 * parameter; for aggregates, we might be dealing with an ordered
! 			 * set function which have more complex rules for variadics, so
! 			 * punt the error checking for that case to the caller.
! 			 */
! 			if (varCount > 0 && !is_aggregate)
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  						 errmsg("VARIADIC parameter must be the last input parameter")));
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
***************
*** 4410,4415 **** ExecInitExpr(Expr *node, PlanState *parent)
--- 4410,4416 ----
  
  					astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
  														 parent);
+ 					astate->orddirectargs = (List *) ExecInitExpr((Expr *) aggref->orddirectargs, parent);
  					astate->aggfilter = ExecInitExpr(aggref->aggfilter,
  													 parent);
  
*** a/src/backend/executor/functions.c
--- b/src/backend/executor/functions.c
***************
*** 380,387 **** sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
  		param = ParseFuncOrColumn(pstate,
  								  list_make1(subfield),
  								  list_make1(param),
! 								  NIL, NULL, false, false, false,
! 								  NULL, true, cref->location);
  	}
  
  	return param;
--- 380,387 ----
  		param = ParseFuncOrColumn(pstate,
  								  list_make1(subfield),
  								  list_make1(param),
! 								  cref->location,
! 								  NULL);
  	}
  
  	return param;
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
***************
*** 3,9 ****
   * nodeAgg.c
   *	  Routines to handle aggregate nodes.
   *
!  *	  ExecAgg evaluates each aggregate in the following steps:
   *
   *		 transvalue = initcond
   *		 foreach input_tuple do
--- 3,9 ----
   * nodeAgg.c
   *	  Routines to handle aggregate nodes.
   *
!  *	  ExecAgg evaluates each normal aggregate in the following steps:
   *
   *		 transvalue = initcond
   *		 foreach input_tuple do
***************
*** 66,71 ****
--- 66,91 ----
   *	  AggState is available as context in earlier releases (back to 8.1),
   *	  but direct examination of the node is needed to use it before 9.0.
   *
+  *---
+  *
+  *    Ordered set functions modify the above process in a number of ways.
+  *    Most importantly, they do not have transfuncs at all; the same sort
+  *    mechanism used for ORDER BY/DISTINCT as described above is used to
+  *    process the input, but then the finalfunc is called without actually
+  *    running the sort (the finalfunc is allowed to insert rows first).
+  *    The finalfunc has access via a set of AggSet* API functions to the
+  *    Tuplesortstate, row count in the group, and other ancillary info.
+  *
+  *    Ordered set functions can, however, have a transvalue declared; this is
+  *    treated as a constant, and added to the end of the sort fields.
+  *    Hypothetical set functions use this to provide a flag that distinguishes
+  *    the hypothetical row from the input data.
+  *
+  *    Since they have no transfunc, ordered set functions have their own
+  *    'strict' flag stored in the aggregate's own pg_proc entry; this affects
+  *    whether rows containing nulls are placed in the sorter. But since we
+  *    pass dummy null arguments to the finalfunc for type resolution purposes,
+  *    no ordered set finalfunc is allowed to be strict.
   *
   * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
   * Portions Copyright (c) 1994, Regents of the University of California
***************
*** 87,96 ****
--- 107,118 ----
  #include "executor/nodeAgg.h"
  #include "miscadmin.h"
  #include "nodes/nodeFuncs.h"
+ #include "nodes/makefuncs.h"
  #include "optimizer/clauses.h"
  #include "optimizer/tlist.h"
  #include "parser/parse_agg.h"
  #include "parser/parse_coerce.h"
+ #include "parser/parse_clause.h"
  #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
***************
*** 105,110 ****
--- 127,134 ----
   */
  typedef struct AggStatePerAggData
  {
+ 	NodeTag type;
+ 	
  	/*
  	 * These values are set up during ExecInitAgg() and do not change
  	 * thereafter:
***************
*** 114,123 **** typedef struct AggStatePerAggData
  	AggrefExprState *aggrefstate;
  	Aggref	   *aggref;
  
! 	/* number of input arguments for aggregate function proper */
  	int			numArguments;
  
! 	/* number of inputs including ORDER BY expressions */
  	int			numInputs;
  
  	/* Oids of transfer functions */
--- 138,162 ----
  	AggrefExprState *aggrefstate;
  	Aggref	   *aggref;
  
! 	/* Pointer to parent AggState node */
! 	AggState   *aggstate;
! 
! 	/* copied from aggref */
! 	bool        isOrderedSet;
! 
! 	/*
! 	 * number of arguments for aggregate function proper.
! 	 * For ordered set functions, this includes the ORDER BY
! 	 * columns, *except* in the case of hypothetical set functions.
! 	 */
  	int			numArguments;
  
! 	/*
! 	 * number of inputs including ORDER BY expressions. For ordered
! 	 * set functions, *only* the ORDER BY expressions are included
! 	 * here, since the direct args to the function are not properly
! 	 * "input" in the sense of being derived from the tuple group.
! 	 */
  	int			numInputs;
  
  	/* Oids of transfer functions */
***************
*** 126,137 **** typedef struct AggStatePerAggData
  
  	/*
  	 * fmgr lookup data for transfer functions --- only valid when
! 	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
! 	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
  	FmgrInfo	finalfn;
  
  	/* Input collation derived for aggregate */
  	Oid			aggCollation;
  
--- 165,187 ----
  
  	/*
  	 * fmgr lookup data for transfer functions --- only valid when
! 	 * corresponding oid is not InvalidOid.
  	 */
  	FmgrInfo	transfn;
  	FmgrInfo	finalfn;
  
+ 	/*
+ 	 * If >0, aggregate as a whole is strict (skips null input)
+ 	 * The value specifies how many columns to check; normal aggs
+ 	 * only check numArguments, while ordered set functions check
+ 	 * numInputs.
+ 	 *
+ 	 * Ordered set functions are not allowed to have strict finalfns;
+ 	 * other aggregates respect the finalfn strict flag in the
+ 	 * FmgrInfo above.
+ 	 */
+ 	int         numStrict;
+ 
  	/* Input collation derived for aggregate */
  	Oid			aggCollation;
  
***************
*** 148,153 **** typedef struct AggStatePerAggData
--- 198,206 ----
  	Oid		   *sortCollations;
  	bool	   *sortNullsFirst;
  
+ 	/* just for convenience of ordered set funcs, not used here */
+ 	Oid		   *sortEqOperators;
+ 
  	/*
  	 * fmgr lookup data for input columns' equality operators --- only
  	 * set/used when aggregate has DISTINCT flag.  Note that these are in
***************
*** 204,209 **** typedef struct AggStatePerAggData
--- 257,265 ----
  	 */
  
  	Tuplesortstate *sortstate;	/* sort object, if DISTINCT or ORDER BY */
+ 
+ 	int64 number_of_rows;		/* number of rows */
+ 
  }	AggStatePerAggData;
  
  /*
***************
*** 300,305 **** initialize_aggregates(AggState *aggstate,
--- 356,363 ----
  		AggStatePerAgg peraggstate = &peragg[aggno];
  		AggStatePerGroup pergroupstate = &pergroup[aggno];
  
+ 		peraggstate->number_of_rows = 0;
+ 
  		/*
  		 * Start a fresh sort operation for each DISTINCT/ORDER BY aggregate.
  		 */
***************
*** 383,396 **** advance_transition_function(AggState *aggstate,
  	MemoryContext oldContext;
  	Datum		newVal;
  	int			i;
  
! 	if (peraggstate->transfn.fn_strict)
  	{
  		/*
  		 * For a strict transfn, nothing happens when there's a NULL input; we
  		 * just keep the prior transValue.
  		 */
! 		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
  				return;
--- 441,457 ----
  	MemoryContext oldContext;
  	Datum		newVal;
  	int			i;
+ 	int         numStrict = peraggstate->numStrict;
  
! 	Assert(OidIsValid(peraggstate->transfn_oid));
! 
! 	if (numStrict > 0)
  	{
  		/*
  		 * For a strict transfn, nothing happens when there's a NULL input; we
  		 * just keep the prior transValue.
  		 */
! 		for (i = 1; i <= numStrict; i++)
  		{
  			if (fcinfo->argnull[i])
  				return;
***************
*** 506,529 **** advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
  
  		if (peraggstate->numSortCols > 0)
  		{
  			/* DISTINCT and/or ORDER BY case */
  			Assert(slot->tts_nvalid == peraggstate->numInputs);
  
  			/*
! 			 * If the transfn is strict, we want to check for nullity before
  			 * storing the row in the sorter, to save space if there are a lot
! 			 * of nulls.  Note that we must only check numArguments columns,
! 			 * not numInputs, since nullity in columns used only for sorting
! 			 * is not relevant here.
  			 */
! 			if (peraggstate->transfn.fn_strict)
  			{
! 				for (i = 0; i < nargs; i++)
  				{
  					if (slot->tts_isnull[i])
  						break;
  				}
! 				if (i < nargs)
  					continue;
  			}
  
--- 567,590 ----
  
  		if (peraggstate->numSortCols > 0)
  		{
+ 			int numStrict = peraggstate->numStrict;
+ 
  			/* DISTINCT and/or ORDER BY case */
  			Assert(slot->tts_nvalid == peraggstate->numInputs);
  
  			/*
! 			 * If the aggregate is strict, we want to check for nullity before
  			 * storing the row in the sorter, to save space if there are a lot
! 			 * of nulls.
  			 */
! 			if (numStrict > 0)
  			{
! 				for (i = 0; i < numStrict; i++)
  				{
  					if (slot->tts_isnull[i])
  						break;
  				}
! 				if (i < numStrict)
  					continue;
  			}
  
***************
*** 534,539 **** advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
--- 595,602 ----
  								   slot->tts_isnull[0]);
  			else
  				tuplesort_puttupleslot(peraggstate->sortstate, slot);
+ 
+ 			peraggstate->number_of_rows++;
  		}
  		else
  		{
***************
*** 756,770 **** finalize_aggregate(AggState *aggstate,
  	if (OidIsValid(peraggstate->finalfn_oid))
  	{
  		FunctionCallInfoData fcinfo;
  
! 		InitFunctionCallInfoData(fcinfo, &(peraggstate->finalfn), 1,
! 								 peraggstate->aggCollation,
! 								 (void *) aggstate, NULL);
! 		fcinfo.arg[0] = pergroupstate->transValue;
! 		fcinfo.argnull[0] = pergroupstate->transValueIsNull;
! 		if (fcinfo.flinfo->fn_strict && pergroupstate->transValueIsNull)
  		{
! 			/* don't call a strict function with NULL inputs */
  			*resultVal = (Datum) 0;
  			*resultIsNull = true;
  		}
--- 819,884 ----
  	if (OidIsValid(peraggstate->finalfn_oid))
  	{
  		FunctionCallInfoData fcinfo;
+ 		bool isnull = false;
+ 
+ 		if (!(peraggstate->isOrderedSet))
+ 		{
+ 			InitFunctionCallInfoData(fcinfo,
+ 									 &(peraggstate->finalfn),
+ 									 1,
+ 									 peraggstate->aggCollation,
+ 									 (void *) aggstate,
+ 									 NULL);
+ 
+ 			fcinfo.arg[0] = pergroupstate->transValue;
+ 			fcinfo.argnull[0] = isnull = pergroupstate->transValueIsNull;
+ 		}
+ 		else
+ 		{
+ 			List     *args = peraggstate->aggrefstate->orddirectargs;
+ 			ListCell *lc;
+ 			int       i = 0;
+ 			int       numArguments = peraggstate->numArguments;
+ 
+ 			ExecClearTuple(peraggstate->evalslot);
+ 			ExecClearTuple(peraggstate->uniqslot);
+ 
+ 			InitFunctionCallInfoData(fcinfo,
+ 									 &(peraggstate->finalfn),
+ 									 peraggstate->numArguments,
+ 									 peraggstate->aggCollation,
+ 									 (void *) peraggstate,
+ 									 NULL);
+ 
+ 			foreach (lc, args)
+ 			{
+ 				ExprState *expr = (ExprState *) lfirst(lc);
+ 
+ 				fcinfo.arg[i] = ExecEvalExpr(expr,
+ 											 aggstate->ss.ps.ps_ExprContext,
+ 											 &fcinfo.argnull[i],
+ 											 NULL);
+ 				if (fcinfo.argnull[i])
+ 					isnull = true;
  
! 				++i;
! 			}
! 
! 			for(; i < numArguments; i++)
! 			{
! 				fcinfo.arg[i] = (Datum) 0;
! 				fcinfo.argnull[i] = true;
! 				isnull = true;
! 			}
! 		}
! 
! 		if (isnull && fcinfo.flinfo->fn_strict)
  		{
! 			/*
! 			 * don't call a strict function with NULL inputs; for ordered set
! 			 * functions this is paranoia, we already required that fn_strict
! 			 * is false, but easy to check anyway
! 			 */
  			*resultVal = (Datum) 0;
  			*resultIsNull = true;
  		}
***************
*** 1164,1169 **** agg_retrieve_direct(AggState *aggstate)
--- 1278,1294 ----
  		}
  
  		/*
+ 		 * Use the representative input tuple for any references to
+ 		 * non-aggregated input columns in the qual and tlist.	(If we are not
+ 		 * grouping, and there are no input rows at all, we will come here
+ 		 * with an empty firstSlot ... but if not grouping, there can't be any
+ 		 * references to non-aggregated input columns, so no problem.)
+ 		 * We do this before finalizing because for ordered set functions,
+ 		 * finalize_aggregates can evaluate arguments referencing the tuple.
+ 		 */
+ 		econtext->ecxt_outertuple = firstSlot;
+ 
+ 		/*
  		 * Done scanning input tuple group. Finalize each aggregate
  		 * calculation, and stash results in the per-output-tuple context.
  		 */
***************
*** 1174,1187 **** agg_retrieve_direct(AggState *aggstate)
  
  			if (peraggstate->numSortCols > 0)
  			{
! 				if (peraggstate->numInputs == 1)
! 					process_ordered_aggregate_single(aggstate,
! 													 peraggstate,
! 													 pergroupstate);
! 				else
! 					process_ordered_aggregate_multi(aggstate,
! 													peraggstate,
! 													pergroupstate);
  			}
  
  			finalize_aggregate(aggstate, peraggstate, pergroupstate,
--- 1299,1315 ----
  
  			if (peraggstate->numSortCols > 0)
  			{
! 				if (!(peraggstate->isOrderedSet))
! 				{
! 					if (peraggstate->numInputs == 1)
! 						process_ordered_aggregate_single(aggstate,
! 														 peraggstate,
! 														 pergroupstate);
! 					else
! 						process_ordered_aggregate_multi(aggstate,
! 														peraggstate,
! 														pergroupstate);
! 				}
  			}
  
  			finalize_aggregate(aggstate, peraggstate, pergroupstate,
***************
*** 1189,1203 **** agg_retrieve_direct(AggState *aggstate)
  		}
  
  		/*
- 		 * Use the representative input tuple for any references to
- 		 * non-aggregated input columns in the qual and tlist.	(If we are not
- 		 * grouping, and there are no input rows at all, we will come here
- 		 * with an empty firstSlot ... but if not grouping, there can't be any
- 		 * references to non-aggregated input columns, so no problem.)
- 		 */
- 		econtext->ecxt_outertuple = firstSlot;
- 
- 		/*
  		 * Check the qual (HAVING clause); if the group does not match, ignore
  		 * it and loop back to try to process another group.
  		 */
--- 1317,1322 ----
***************
*** 1568,1577 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
--- 1687,1698 ----
  		int			numInputs;
  		int			numSortCols;
  		int			numDistinctCols;
+ 		bool        isOrderedSet = aggref->isordset;
  		List	   *sortlist;
  		HeapTuple	aggTuple;
  		Form_pg_aggregate aggform;
  		Oid			aggtranstype;
+ 		Oid			aggtranstypecoll;
  		AclResult	aclresult;
  		Oid			transfn_oid,
  					finalfn_oid;
***************
*** 1580,1585 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
--- 1701,1710 ----
  		Datum		textInitVal;
  		int			i;
  		ListCell   *lc;
+         bool        is_strict;
+ 		Oid			inputCollations[FUNC_MAX_ARGS];
+ 		List       *argexprs;
+ 		List       *argexprstate;
  
  		/* Planner should have assigned aggregate to correct level */
  		Assert(aggref->agglevelsup == 0);
***************
*** 1601,1631 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  		/* Nope, so assign a new PerAgg record */
  		peraggstate = &peragg[++aggno];
  
- 		/* Mark Aggref state node with assigned index in the result array */
- 		aggrefstate->aggno = aggno;
- 
  		/* Fill in the peraggstate data */
! 		peraggstate->aggrefstate = aggrefstate;
  		peraggstate->aggref = aggref;
! 		numInputs = list_length(aggref->args);
! 		peraggstate->numInputs = numInputs;
! 		peraggstate->sortstate = NULL;
  
! 		/*
! 		 * Get actual datatypes of the inputs.	These could be different from
! 		 * the agg's declared input types, when the agg accepts ANY or a
! 		 * polymorphic type.
! 		 */
! 		numArguments = 0;
! 		foreach(lc, aggref->args)
! 		{
! 			TargetEntry *tle = (TargetEntry *) lfirst(lc);
  
! 			if (!tle->resjunk)
! 				inputTypes[numArguments++] = exprType((Node *) tle->expr);
! 		}
! 		peraggstate->numArguments = numArguments;
  
  		aggTuple = SearchSysCache1(AGGFNOID,
  								   ObjectIdGetDatum(aggref->aggfnoid));
  		if (!HeapTupleIsValid(aggTuple))
--- 1726,1743 ----
  		/* Nope, so assign a new PerAgg record */
  		peraggstate = &peragg[++aggno];
  
  		/* Fill in the peraggstate data */
! 		peraggstate->type = T_AggStatePerAggData;
! 		peraggstate->aggstate = aggstate;
  		peraggstate->aggref = aggref;
! 		peraggstate->aggrefstate = aggrefstate;
  
! 		peraggstate->isOrderedSet = isOrderedSet;
  
! 		/* Mark Aggref state node with assigned index in the result array */
! 		aggrefstate->aggno = aggno;
  
+ 		/* Fetch the pg_aggregate row */
  		aggTuple = SearchSysCache1(AGGFNOID,
  								   ObjectIdGetDatum(aggref->aggfnoid));
  		if (!HeapTupleIsValid(aggTuple))
***************
*** 1633,1638 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
--- 1745,1757 ----
  				 aggref->aggfnoid);
  		aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
  
+ 		/*
+ 		 * Check that the definition hasn't somehow changed incompatibly.
+ 		 */
+ 		if (isOrderedSet != (aggform->aggisordsetfunc)
+ 			|| (aggref->ishypothetical != (aggform->aggordnargs == -2)))
+ 			elog(ERROR, "Incompatible change to aggregate definition");
+ 
  		/* Check permission to call aggregate function */
  		aclresult = pg_proc_aclcheck(aggref->aggfnoid, GetUserId(),
  									 ACL_EXECUTE);
***************
*** 1644,1668 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  		peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
  		peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
  
! 		/* Check that aggregate owner has permission to call component fns */
  		{
  			HeapTuple	procTuple;
  			Oid			aggOwner;
  
  			procTuple = SearchSysCache1(PROCOID,
  										ObjectIdGetDatum(aggref->aggfnoid));
  			if (!HeapTupleIsValid(procTuple))
  				elog(ERROR, "cache lookup failed for function %u",
  					 aggref->aggfnoid);
! 			aggOwner = ((Form_pg_proc) GETSTRUCT(procTuple))->proowner;
  			ReleaseSysCache(procTuple);
  
! 			aclresult = pg_proc_aclcheck(transfn_oid, aggOwner,
! 										 ACL_EXECUTE);
! 			if (aclresult != ACLCHECK_OK)
  				aclcheck_error(aclresult, ACL_KIND_PROC,
  							   get_func_name(transfn_oid));
! 			InvokeFunctionExecuteHook(transfn_oid);
  			if (OidIsValid(finalfn_oid))
  			{
  				aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
--- 1763,1799 ----
  		peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
  		peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
  
! 		/*
! 		 * Check that aggregate owner has permission to call component fns
! 		 * In passing, fetch the proisstrict flag for the aggregate proper,
! 		 * which subs for the transfn's strictness flag in cases where there
! 		 * is no transfn.
! 		 */
  		{
  			HeapTuple	procTuple;
  			Oid			aggOwner;
+ 			Form_pg_proc procp;
  
  			procTuple = SearchSysCache1(PROCOID,
  										ObjectIdGetDatum(aggref->aggfnoid));
  			if (!HeapTupleIsValid(procTuple))
  				elog(ERROR, "cache lookup failed for function %u",
  					 aggref->aggfnoid);
! 			procp = (Form_pg_proc) GETSTRUCT(procTuple);
! 			aggOwner = procp->proowner;
! 			is_strict = procp->proisstrict;
  			ReleaseSysCache(procTuple);
  
! 			if (OidIsValid(transfn_oid))
! 			{
! 				aclresult = pg_proc_aclcheck(transfn_oid, aggOwner,
! 									         ACL_EXECUTE);
! 				if (aclresult != ACLCHECK_OK && OidIsValid(transfn_oid))
  				aclcheck_error(aclresult, ACL_KIND_PROC,
  							   get_func_name(transfn_oid));
! 				InvokeFunctionExecuteHook(transfn_oid);
! 			}
! 
  			if (OidIsValid(finalfn_oid))
  			{
  				aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
***************
*** 1674,1690 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  			}
  		}
  
  		/* resolve actual type of transition state, if polymorphic */
  		aggtranstype = aggform->aggtranstype;
! 		if (IsPolymorphicType(aggtranstype))
  		{
  			/* have to fetch the agg's declared input types... */
  			Oid		   *declaredArgTypes;
! 			int			agg_nargs;
  
  			(void) get_func_signature(aggref->aggfnoid,
! 									  &declaredArgTypes, &agg_nargs);
! 			Assert(agg_nargs == numArguments);
  			aggtranstype = enforce_generic_type_consistency(inputTypes,
  															declaredArgTypes,
  															agg_nargs,
--- 1805,1841 ----
  			}
  		}
  
+ 		/*
+ 		 * Get actual datatypes of the inputs.	These could be different from
+ 		 * the agg's declared input types, when the agg accepts ANY or a
+ 		 * polymorphic type.
+ 		 */
+ 
+ 		peraggstate->numInputs = numInputs = list_length(aggref->args);
+ 
+ 		numArguments = get_aggregate_argtypes(aggref,
+ 											  inputTypes,
+ 											  inputCollations);
+ 
  		/* resolve actual type of transition state, if polymorphic */
  		aggtranstype = aggform->aggtranstype;
! 		if (OidIsValid(aggtranstype) && IsPolymorphicType(aggtranstype))
  		{
  			/* have to fetch the agg's declared input types... */
  			Oid		   *declaredArgTypes;
! 			int         agg_nargs;
  
  			(void) get_func_signature(aggref->aggfnoid,
! 									  &declaredArgTypes,
! 									  &agg_nargs);
! 
! 			/*
! 			 * if variadic "any", might be more actual args than declared
! 			 * args, but these extra args can't influence the determination
! 			 * of polymorphic transition or result type.
! 			 */
! 			Assert(agg_nargs <= numArguments);
! 
  			aggtranstype = enforce_generic_type_consistency(inputTypes,
  															declaredArgTypes,
  															agg_nargs,
***************
*** 1693,1727 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  			pfree(declaredArgTypes);
  		}
  
  		/* build expression trees using actual argument & result types */
! 		build_aggregate_fnexprs(inputTypes,
! 								numArguments,
! 								aggref->aggvariadic,
! 								aggtranstype,
! 								aggref->aggtype,
! 								aggref->inputcollid,
! 								transfn_oid,
! 								finalfn_oid,
! 								&transfnexpr,
! 								&finalfnexpr);
! 
! 		fmgr_info(transfn_oid, &peraggstate->transfn);
! 		fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
  
  		if (OidIsValid(finalfn_oid))
  		{
  			fmgr_info(finalfn_oid, &peraggstate->finalfn);
  			fmgr_info_set_expr((Node *) finalfnexpr, &peraggstate->finalfn);
  		}
  
  		peraggstate->aggCollation = aggref->inputcollid;
  
  		get_typlenbyval(aggref->aggtype,
  						&peraggstate->resulttypeLen,
  						&peraggstate->resulttypeByVal);
! 		get_typlenbyval(aggtranstype,
! 						&peraggstate->transtypeLen,
! 						&peraggstate->transtypeByVal);
  
  		/*
  		 * initval is potentially null, so don't try to access it as a struct
--- 1844,1925 ----
  			pfree(declaredArgTypes);
  		}
  
+ 		aggtranstypecoll = get_typcollation(aggtranstype);
+ 
  		/* build expression trees using actual argument & result types */
! 
! 		if (!isOrderedSet)
! 		{
! 			build_aggregate_fnexprs(inputTypes,
! 									numArguments,
! 									aggref->aggvariadic,
! 									aggtranstype,
! 									aggref->aggtype,
! 									aggref->inputcollid,
! 									transfn_oid,
! 									finalfn_oid,
! 									&transfnexpr,
! 									&finalfnexpr);
! 		}
! 		else
! 		{
! 			/*
! 			 * The transvalue counts as an argument, but not for hypothetical
! 			 * set funcs.
! 			 */
! 			if (OidIsValid(aggtranstype) && !(aggref->ishypothetical))
! 			{
! 				if (numArguments == FUNC_MAX_ARGS)
! 					elog(ERROR, "Too many arguments to ordered set function");
! 
! 				inputTypes[numArguments++] = aggtranstype;
! 				inputCollations[numArguments++] = aggtranstypecoll;
! 			}
! 
! 			build_orderedset_fnexprs(inputTypes,
! 									 numArguments,
! 									 aggref->aggvariadic,
! 									 aggref->aggtype,
! 									 aggref->inputcollid,
! 									 inputCollations,
! 									 finalfn_oid,
! 									 &finalfnexpr);
! 		}
! 
! 		peraggstate->numArguments = numArguments;
! 
! 		if (OidIsValid(transfn_oid))
! 		{
! 			fmgr_info(transfn_oid, &peraggstate->transfn);
! 			fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
! 
! 			is_strict = peraggstate->transfn.fn_strict;
! 		}
  
  		if (OidIsValid(finalfn_oid))
  		{
  			fmgr_info(finalfn_oid, &peraggstate->finalfn);
  			fmgr_info_set_expr((Node *) finalfnexpr, &peraggstate->finalfn);
+ 			if (peraggstate->finalfn.fn_strict && isOrderedSet)
+ 				elog(ERROR, "Ordered set finalfns must not be strict");
  		}
  
+ 		if (is_strict)
+ 			peraggstate->numStrict = (isOrderedSet ? numInputs : numArguments);
+ 		else
+ 			peraggstate->numStrict = 0;
+ 
  		peraggstate->aggCollation = aggref->inputcollid;
  
  		get_typlenbyval(aggref->aggtype,
  						&peraggstate->resulttypeLen,
  						&peraggstate->resulttypeByVal);
! 		if (OidIsValid(aggtranstype))
! 		{
! 			get_typlenbyval(aggtranstype,
! 							&peraggstate->transtypeLen,
! 							&peraggstate->transtypeByVal);
! 		}
  
  		/*
  		 * initval is potentially null, so don't try to access it as a struct
***************
*** 1744,1750 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  		 * transValue.	This should have been checked at agg definition time,
  		 * but just in case...
  		 */
! 		if (peraggstate->transfn.fn_strict && peraggstate->initValueIsNull)
  		{
  			if (numArguments < 1 ||
  				!IsBinaryCoercible(inputTypes[0], aggtranstype))
--- 1942,1950 ----
  		 * transValue.	This should have been checked at agg definition time,
  		 * but just in case...
  		 */
! 		if (OidIsValid(peraggstate->transfn_oid)
! 			&& peraggstate->transfn.fn_strict
! 			&& peraggstate->initValueIsNull)
  		{
  			if (numArguments < 1 ||
  				!IsBinaryCoercible(inputTypes[0], aggtranstype))
***************
*** 1754,1774 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  								aggref->aggfnoid)));
  		}
  
! 		/*
! 		 * Get a tupledesc corresponding to the inputs (including sort
! 		 * expressions) of the agg.
! 		 */
! 		peraggstate->evaldesc = ExecTypeFromTL(aggref->args, false);
! 
! 		/* Create slot we're going to do argument evaluation in */
! 		peraggstate->evalslot = ExecInitExtraTupleSlot(estate);
! 		ExecSetSlotDescriptor(peraggstate->evalslot, peraggstate->evaldesc);
! 
! 		/* Set up projection info for evaluation */
! 		peraggstate->evalproj = ExecBuildProjectionInfo(aggrefstate->args,
! 														aggstate->tmpcontext,
! 														peraggstate->evalslot,
! 														NULL);
  
  		/*
  		 * If we're doing either DISTINCT or ORDER BY, then we have a list of
--- 1954,1961 ----
  								aggref->aggfnoid)));
  		}
  
! 		argexprs = aggref->args;
! 		argexprstate = aggrefstate->args;
  
  		/*
  		 * If we're doing either DISTINCT or ORDER BY, then we have a list of
***************
*** 1777,1782 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
--- 1964,1974 ----
  		 *
  		 * Note that by construction, if there is a DISTINCT clause then the
  		 * ORDER BY clause is a prefix of it (see transformDistinctClause).
+ 		 *
+ 		 * If we're doing an ordered set function, though, we want to do the
+ 		 * initialization for DISTINCT since the ordered set finalfn might
+ 		 * want it, and it's much easier to do it here. So set numDistinctCols
+ 		 * and let the later initialization take care of it.
  		 */
  		if (aggref->aggdistinct)
  		{
***************
*** 1788,1798 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  		{
  			sortlist = aggref->aggorder;
  			numSortCols = list_length(sortlist);
! 			numDistinctCols = 0;
  		}
  
  		peraggstate->numSortCols = numSortCols;
  		peraggstate->numDistinctCols = numDistinctCols;
  
  		if (numSortCols > 0)
  		{
--- 1980,2065 ----
  		{
  			sortlist = aggref->aggorder;
  			numSortCols = list_length(sortlist);
! 			numDistinctCols = isOrderedSet ? numSortCols : 0;
! 		}
! 
! 		/*
! 		 * If this is an ordered set function, and we have a transtype, then
! 		 * it represents an extra column to be added to the sorter with a
! 		 * fixed value. Plus, if aggtranssortop is valid, we have to include
! 		 * a sort entry for the new column.
! 		 *
! 		 * I'd probably have done this in the planner if I'd seen any
! 		 * possible place to put it; if there is one, it's very obscure.
! 		 */
! 
! 		if (OidIsValid(aggtranstype) && isOrderedSet)
! 		{
! 			Oid sortop = aggform->aggtranssortop;
! 			Const *node = makeNode(Const);
! 			TargetEntry *tle;
! 			SortGroupClause *sortcl = NULL;
! 
! 			node->consttype = aggtranstype;
! 			node->consttypmod = -1;
! 			node->constcollid = aggtranstypecoll;
! 			node->constlen = peraggstate->transtypeLen;
! 			node->constvalue = peraggstate->initValue;
! 			node->constisnull = peraggstate->initValueIsNull;
! 			node->constbyval = peraggstate->transtypeByVal;
! 			node->location = -1;
! 
! 			tle = makeTargetEntry((Expr *) node,
! 								  ++numInputs,
! 								  NULL,
! 								  true);
! 
! 			peraggstate->numInputs = numInputs;
! 
! 			if (OidIsValid(sortop))
! 			{
! 				Assert(aggref->aggdistinct == NIL);
! 
! 				sortcl = makeNode(SortGroupClause);
! 
! 				sortcl->tleSortGroupRef = assignSortGroupRef(tle, argexprs);
! 
! 				sortcl->sortop = sortop;
! 				sortcl->hashable = false;
! 				sortcl->eqop = get_equality_op_for_ordering_op(sortop,
! 															   &sortcl->nulls_first);
! 
! 				sortlist = lappend(list_copy(sortlist), sortcl);
! 				++numSortCols;
! 				++numDistinctCols;
! 			}
! 
! 			/* shallow-copy the passed-in lists, which we must not scribble on. */
! 
! 			argexprs = lappend(list_copy(argexprs), (Node *) tle);
! 			argexprstate = lappend(list_copy(argexprstate),
! 								   ExecInitExpr((Expr *) tle, (PlanState *) aggstate));
  		}
  
  		peraggstate->numSortCols = numSortCols;
  		peraggstate->numDistinctCols = numDistinctCols;
+ 		peraggstate->sortstate = NULL;
+ 
+ 		/*
+ 		 * Get a tupledesc corresponding to the inputs (including sort
+ 		 * expressions) of the agg.
+ 		 */
+ 		peraggstate->evaldesc = ExecTypeFromTL(argexprs, false);
+ 
+ 		/* Create slot we're going to do argument evaluation in */
+ 		peraggstate->evalslot = ExecInitExtraTupleSlot(estate);
+ 		ExecSetSlotDescriptor(peraggstate->evalslot, peraggstate->evaldesc);
+ 
+ 		/* Set up projection info for evaluation */
+ 		peraggstate->evalproj = ExecBuildProjectionInfo(argexprstate,
+ 														aggstate->tmpcontext,
+ 														peraggstate->evalslot,
+ 														NULL);
  
  		if (numSortCols > 0)
  		{
***************
*** 1805,1815 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  			/* If we have only one input, we need its len/byval info. */
  			if (numInputs == 1)
  			{
! 				get_typlenbyval(inputTypes[0],
  								&peraggstate->inputtypeLen,
  								&peraggstate->inputtypeByVal);
  			}
! 			else if (numDistinctCols > 0)
  			{
  				/* we will need an extra slot to store prior values */
  				peraggstate->uniqslot = ExecInitExtraTupleSlot(estate);
--- 2072,2083 ----
  			/* If we have only one input, we need its len/byval info. */
  			if (numInputs == 1)
  			{
! 				get_typlenbyval(peraggstate->evaldesc->attrs[0]->atttypid,
  								&peraggstate->inputtypeLen,
  								&peraggstate->inputtypeByVal);
  			}
! 
! 			if (numDistinctCols > 0 && (numInputs > 1 || isOrderedSet))
  			{
  				/* we will need an extra slot to store prior values */
  				peraggstate->uniqslot = ExecInitExtraTupleSlot(estate);
***************
*** 1822,1871 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  				(AttrNumber *) palloc(numSortCols * sizeof(AttrNumber));
  			peraggstate->sortOperators =
  				(Oid *) palloc(numSortCols * sizeof(Oid));
  			peraggstate->sortCollations =
  				(Oid *) palloc(numSortCols * sizeof(Oid));
  			peraggstate->sortNullsFirst =
  				(bool *) palloc(numSortCols * sizeof(bool));
  
  			i = 0;
  			foreach(lc, sortlist)
  			{
  				SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
  				TargetEntry *tle = get_sortgroupclause_tle(sortcl,
! 														   aggref->args);
  
  				/* the parser should have made sure of this */
  				Assert(OidIsValid(sortcl->sortop));
  
  				peraggstate->sortColIdx[i] = tle->resno;
  				peraggstate->sortOperators[i] = sortcl->sortop;
  				peraggstate->sortCollations[i] = exprCollation((Node *) tle->expr);
  				peraggstate->sortNullsFirst[i] = sortcl->nulls_first;
- 				i++;
- 			}
- 			Assert(i == numSortCols);
- 		}
  
! 		if (aggref->aggdistinct)
! 		{
! 			Assert(numArguments > 0);
! 
! 			/*
! 			 * We need the equal function for each DISTINCT comparison we will
! 			 * make.
! 			 */
! 			peraggstate->equalfns =
! 				(FmgrInfo *) palloc(numDistinctCols * sizeof(FmgrInfo));
! 
! 			i = 0;
! 			foreach(lc, aggref->aggdistinct)
! 			{
! 				SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
  
- 				fmgr_info(get_opcode(sortcl->eqop), &peraggstate->equalfns[i]);
  				i++;
  			}
! 			Assert(i == numDistinctCols);
  		}
  
  		ReleaseSysCache(aggTuple);
--- 2090,2136 ----
  				(AttrNumber *) palloc(numSortCols * sizeof(AttrNumber));
  			peraggstate->sortOperators =
  				(Oid *) palloc(numSortCols * sizeof(Oid));
+ 			peraggstate->sortEqOperators =
+ 				(Oid *) palloc(numSortCols * sizeof(Oid));
  			peraggstate->sortCollations =
  				(Oid *) palloc(numSortCols * sizeof(Oid));
  			peraggstate->sortNullsFirst =
  				(bool *) palloc(numSortCols * sizeof(bool));
+ 			if (numDistinctCols > 0)
+ 				peraggstate->equalfns =
+ 					(FmgrInfo *) palloc(numDistinctCols * sizeof(FmgrInfo));
+ 			else
+ 				peraggstate->equalfns = NULL;
+ 			
  
  			i = 0;
  			foreach(lc, sortlist)
  			{
  				SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
  				TargetEntry *tle = get_sortgroupclause_tle(sortcl,
! 														   argexprs);
  
  				/* the parser should have made sure of this */
  				Assert(OidIsValid(sortcl->sortop));
  
  				peraggstate->sortColIdx[i] = tle->resno;
  				peraggstate->sortOperators[i] = sortcl->sortop;
+ 				peraggstate->sortEqOperators[i] = sortcl->eqop;
  				peraggstate->sortCollations[i] = exprCollation((Node *) tle->expr);
  				peraggstate->sortNullsFirst[i] = sortcl->nulls_first;
  
! 				/*
! 				 * It's OK to get the equalfns here too, since we already
! 				 * require that sortlist is aggref->aggdistinct for the normal
! 				 * distinct case, and for ordered set functions using the
! 				 * (possibly modified copy of) aggref->aggorder is correct
! 				 */
! 				if (peraggstate->equalfns)
! 					fmgr_info(get_opcode(sortcl->eqop), &peraggstate->equalfns[i]);
  
  				i++;
  			}
! 			Assert(i == numSortCols);
  		}
  
  		ReleaseSysCache(aggTuple);
***************
*** 2023,2028 **** ExecReScanAgg(AggState *node)
--- 2288,2296 ----
   * If aggcontext isn't NULL, the function also stores at *aggcontext the
   * identity of the memory context that aggregate transition values are
   * being stored in.
+  *
+  * We do NOT include AGG_CONTEXT_ORDERED as a possible return here, since
+  * that would open a security hole.
   */
  int
  AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
***************
*** 2063,2065 **** aggregate_dummy(PG_FUNCTION_ARGS)
--- 2331,2448 ----
  		 fcinfo->flinfo->fn_oid);
  	return (Datum) 0;			/* keep compiler quiet */
  }
+ 
+ /* AggSetGetRowCount - Get the number of rows in case of ordered set
+  * functions.
+  */
+ int64
+ AggSetGetRowCount(FunctionCallInfo fcinfo)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ 	{
+ 		return ((AggStatePerAggData *)fcinfo->context)->number_of_rows;
+ 	}
+ 
+ 	elog(ERROR, "Called AggSetGetRowCount on non ordered set function");
+ 	return -1;
+ }
+ 
+ /* AggSetGetSortInfo - Get the sort state in the case of 
+  * ordered set functions.
+  */
+ void
+ AggSetGetSortInfo(FunctionCallInfo fcinfo,
+ 				  Tuplesortstate **sortstate,
+ 				  TupleDesc *tupdesc,
+ 				  TupleTableSlot **tupslot,
+ 				  Oid *datumtype)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ 	{
+ 		AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+ 
+ 		*sortstate = peraggstate->sortstate;
+ 		if (peraggstate->numInputs == 1)
+ 		{
+ 			if (tupdesc)
+ 				*tupdesc = NULL;
+ 			if (datumtype)
+ 				*datumtype = peraggstate->evaldesc->attrs[0]->atttypid;
+ 		}
+ 		else
+ 		{
+ 			if (tupdesc)
+ 				*tupdesc = peraggstate->evaldesc;
+ 			if (datumtype)
+ 				*datumtype = InvalidOid;
+ 		}
+ 		
+ 		if (tupslot)
+ 			*tupslot = peraggstate->evalslot;
+ 	}
+ 	else
+ 		elog(ERROR, "AggSetSortInfo called on non ordered set function");
+ }
+ 
+ int
+ AggSetGetDistinctInfo(FunctionCallInfo fcinfo,
+ 					  TupleTableSlot **uniqslot,
+ 					  AttrNumber **sortColIdx,
+ 					  FmgrInfo **equalfns)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ 	{
+ 		AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+ 
+ 		if (uniqslot)
+ 			*uniqslot = peraggstate->uniqslot;
+ 		if (sortColIdx)
+ 			*sortColIdx = peraggstate->sortColIdx;
+ 		if (equalfns)
+ 			*equalfns = peraggstate->equalfns;
+ 
+ 		return peraggstate->numDistinctCols;
+ 	}
+ 	else
+ 		elog(ERROR, "AggSetGetDistinctOperators called on non ordered set function");
+ }
+ 
+ int
+ AggSetGetSortOperators(FunctionCallInfo fcinfo,
+ 					   AttrNumber **sortColIdx,
+ 					   Oid **sortOperators,
+ 					   Oid **sortEqOperators,
+ 					   Oid **sortCollations,
+ 					   bool **sortNullsFirst)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ 	{
+ 		AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+ 
+ 		if (sortColIdx)
+ 			*sortColIdx = peraggstate->sortColIdx;
+ 		if (sortOperators)
+ 			*sortOperators = peraggstate->sortOperators;
+ 		if (sortEqOperators)
+ 			*sortEqOperators = peraggstate->sortEqOperators;
+ 		if (sortCollations)
+ 			*sortCollations = peraggstate->sortCollations;
+ 		if (sortNullsFirst)
+ 			*sortNullsFirst = peraggstate->sortNullsFirst;
+ 
+ 		return peraggstate->numSortCols;
+ 	}
+ 	else
+ 		elog(ERROR, "AggSetGetSortOperators called on non ordered set function");
+ }
+ 
+ void
+ AggSetGetPerTupleContext(FunctionCallInfo fcinfo,
+ 						 MemoryContext *memcontext)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ 	{
+ 		AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+ 		*memcontext = peraggstate->aggstate->tmpcontext->ecxt_per_tuple_memory;
+ 	}
+ }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1139,1147 **** _copyAggref(const Aggref *from)
--- 1139,1150 ----
  	COPY_NODE_FIELD(args);
  	COPY_NODE_FIELD(aggorder);
  	COPY_NODE_FIELD(aggdistinct);
+ 	COPY_NODE_FIELD(orddirectargs);
  	COPY_NODE_FIELD(aggfilter);
  	COPY_SCALAR_FIELD(aggstar);
  	COPY_SCALAR_FIELD(aggvariadic);
+ 	COPY_SCALAR_FIELD(isordset);
+ 	COPY_SCALAR_FIELD(ishypothetical);
  	COPY_SCALAR_FIELD(agglevelsup);
  	COPY_LOCATION_FIELD(location);
  
***************
*** 2174,2179 **** _copyFuncCall(const FuncCall *from)
--- 2177,2183 ----
  	COPY_SCALAR_FIELD(agg_star);
  	COPY_SCALAR_FIELD(agg_distinct);
  	COPY_SCALAR_FIELD(func_variadic);
+ 	COPY_SCALAR_FIELD(has_within_group);
  	COPY_NODE_FIELD(over);
  	COPY_LOCATION_FIELD(location);
  
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 196,204 **** _equalAggref(const Aggref *a, const Aggref *b)
--- 196,207 ----
  	COMPARE_NODE_FIELD(args);
  	COMPARE_NODE_FIELD(aggorder);
  	COMPARE_NODE_FIELD(aggdistinct);
+ 	COMPARE_NODE_FIELD(orddirectargs);
  	COMPARE_NODE_FIELD(aggfilter);
  	COMPARE_SCALAR_FIELD(aggstar);
  	COMPARE_SCALAR_FIELD(aggvariadic);
+ 	COMPARE_SCALAR_FIELD(isordset);
+ 	COMPARE_SCALAR_FIELD(ishypothetical);
  	COMPARE_SCALAR_FIELD(agglevelsup);
  	COMPARE_LOCATION_FIELD(location);
  
***************
*** 2006,2011 **** _equalFuncCall(const FuncCall *a, const FuncCall *b)
--- 2009,2015 ----
  	COMPARE_SCALAR_FIELD(agg_star);
  	COMPARE_SCALAR_FIELD(agg_distinct);
  	COMPARE_SCALAR_FIELD(func_variadic);
+ 	COMPARE_SCALAR_FIELD(has_within_group);
  	COMPARE_NODE_FIELD(over);
  	COMPARE_LOCATION_FIELD(location);
  
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
***************
*** 558,563 **** makeFuncCall(List *name, List *args, int location)
--- 558,564 ----
  	n->agg_star = FALSE;
  	n->agg_distinct = FALSE;
  	n->func_variadic = FALSE;
+ 	n->has_within_group = FALSE;
  	n->over = NULL;
  	return n;
  }
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 1631,1636 **** expression_tree_walker(Node *node,
--- 1631,1641 ----
  				if (expression_tree_walker((Node *) expr->aggdistinct,
  										   walker, context))
  					return true;
+ 
+ 				if (expression_tree_walker((Node *) expr->orddirectargs,
+ 										   walker, context))
+ 					return true;
+ 
  				if (walker((Node *) expr->aggfilter, context))
  					return true;
  			}
***************
*** 2155,2161 **** expression_tree_mutator(Node *node,
--- 2160,2168 ----
  				MUTATE(newnode->args, aggref->args, List *);
  				MUTATE(newnode->aggorder, aggref->aggorder, List *);
  				MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
+ 				MUTATE(newnode->orddirectargs, aggref->orddirectargs, List *);
  				MUTATE(newnode->aggfilter, aggref->aggfilter, Expr *);
+ 
  				return (Node *) newnode;
  			}
  			break;
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 960,968 **** _outAggref(StringInfo str, const Aggref *node)
--- 960,971 ----
  	WRITE_NODE_FIELD(args);
  	WRITE_NODE_FIELD(aggorder);
  	WRITE_NODE_FIELD(aggdistinct);
+ 	WRITE_NODE_FIELD(orddirectargs);
  	WRITE_NODE_FIELD(aggfilter);
  	WRITE_BOOL_FIELD(aggstar);
  	WRITE_BOOL_FIELD(aggvariadic);
+ 	WRITE_BOOL_FIELD(isordset);
+ 	WRITE_BOOL_FIELD(ishypothetical);
  	WRITE_UINT_FIELD(agglevelsup);
  	WRITE_LOCATION_FIELD(location);
  }
***************
*** 2090,2095 **** _outFuncCall(StringInfo str, const FuncCall *node)
--- 2093,2099 ----
  	WRITE_BOOL_FIELD(agg_star);
  	WRITE_BOOL_FIELD(agg_distinct);
  	WRITE_BOOL_FIELD(func_variadic);
+ 	WRITE_BOOL_FIELD(has_within_group);
  	WRITE_NODE_FIELD(over);
  	WRITE_LOCATION_FIELD(location);
  }
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 495,503 **** _readAggref(void)
--- 495,506 ----
  	READ_NODE_FIELD(args);
  	READ_NODE_FIELD(aggorder);
  	READ_NODE_FIELD(aggdistinct);
+ 	READ_NODE_FIELD(orddirectargs);
  	READ_NODE_FIELD(aggfilter);
  	READ_BOOL_FIELD(aggstar);
  	READ_BOOL_FIELD(aggvariadic);
+ 	READ_BOOL_FIELD(isordset);
+ 	READ_BOOL_FIELD(ishypothetical);
  	READ_UINT_FIELD(agglevelsup);
  	READ_LOCATION_FIELD(location);
  
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
***************
*** 39,44 ****
--- 39,45 ----
  #include "parser/analyze.h"
  #include "parser/parse_coerce.h"
  #include "parser/parse_func.h"
+ #include "parser/parse_agg.h"
  #include "rewrite/rewriteManip.h"
  #include "tcop/tcopprot.h"
  #include "utils/acl.h"
***************
*** 464,470 **** count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
  		QualCost	argcosts;
  		Oid		   *inputTypes;
  		int			numArguments;
- 		ListCell   *l;
  
  		Assert(aggref->agglevelsup == 0);
  
--- 465,470 ----
***************
*** 486,492 **** count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
  			costs->numOrderedAggs++;
  
  		/* add component function execution costs to appropriate totals */
! 		costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost;
  		if (OidIsValid(aggfinalfn))
  			costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost;
  
--- 486,493 ----
  			costs->numOrderedAggs++;
  
  		/* add component function execution costs to appropriate totals */
! 		if (OidIsValid(aggtransfn))
! 			costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost;
  		if (OidIsValid(aggfinalfn))
  			costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost;
  
***************
*** 504,575 **** count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
  		costs->transCost.startup += argcosts.startup;
  		costs->transCost.per_tuple += argcosts.per_tuple;
  
! 		/* extract argument types (ignoring any ORDER BY expressions) */
! 		inputTypes = (Oid *) palloc(sizeof(Oid) * list_length(aggref->args));
! 		numArguments = 0;
! 		foreach(l, aggref->args)
  		{
! 			TargetEntry *tle = (TargetEntry *) lfirst(l);
  
! 			if (!tle->resjunk)
! 				inputTypes[numArguments++] = exprType((Node *) tle->expr);
! 		}
  
! 		/* resolve actual type of transition state, if polymorphic */
! 		if (IsPolymorphicType(aggtranstype))
! 		{
! 			/* have to fetch the agg's declared input types... */
! 			Oid		   *declaredArgTypes;
! 			int			agg_nargs;
! 
! 			(void) get_func_signature(aggref->aggfnoid,
! 									  &declaredArgTypes, &agg_nargs);
! 			Assert(agg_nargs == numArguments);
! 			aggtranstype = enforce_generic_type_consistency(inputTypes,
! 															declaredArgTypes,
! 															agg_nargs,
! 															aggtranstype,
! 															false);
! 			pfree(declaredArgTypes);
! 		}
  
! 		/*
! 		 * If the transition type is pass-by-value then it doesn't add
! 		 * anything to the required size of the hashtable.	If it is
! 		 * pass-by-reference then we have to add the estimated size of the
! 		 * value itself, plus palloc overhead.
! 		 */
! 		if (!get_typbyval(aggtranstype))
! 		{
! 			int32		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);
  
! 			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;
  		}
  
  		/*
--- 505,595 ----
  		costs->transCost.startup += argcosts.startup;
  		costs->transCost.per_tuple += argcosts.per_tuple;
  
! 		/*
! 		 * If we're doing a sorted agg, we can punt the entire
! 		 * determination of transition element size since we're not
! 		 * going to be using it to determine hashtable limits. This
! 		 * simplifies the code for hypothetical set functions.
! 		 */
! 
! 		if (aggref->aggorder == NIL && aggref->aggdistinct == NIL)
  		{
! 			Assert(!aggref->isordset);
  
! 			/* extract argument types (ignoring any ORDER BY expressions) */
! 			inputTypes = (Oid *) palloc(sizeof(Oid) * FUNC_MAX_ARGS);
  
! 			numArguments = get_aggregate_argtypes(aggref, inputTypes, NULL);
  
! 			/* resolve actual type of transition state, if polymorphic */
! 			if (OidIsValid(aggtranstype) && IsPolymorphicType(aggtranstype))
! 			{
! 				/* have to fetch the agg's declared input types... */
! 				Oid		   *declaredArgTypes;
! 				int			agg_nargs;
! 
! 				(void) get_func_signature(aggref->aggfnoid,
! 										  &declaredArgTypes, &agg_nargs);
! 
! 				/*
! 				 * if variadic "any", might be more actual args than declared
! 				 * args, but these extra args can't influence the determination
! 				 * of polymorphic transition or result type.
! 				 */
! 				Assert(agg_nargs <= numArguments);
! 
! 				aggtranstype = enforce_generic_type_consistency(inputTypes,
! 																declaredArgTypes,
! 																agg_nargs,
! 																aggtranstype,
! 																false);
! 				pfree(declaredArgTypes);
! 			}
  
  			/*
! 			 * If the transition type is pass-by-value then it doesn't add
! 			 * anything to the required size of the hashtable.	If it is
! 			 * pass-by-reference then we have to add the estimated size of the
! 			 * value itself, plus palloc overhead.
  			 */
! 			if (OidIsValid(aggtranstype) && !get_typbyval(aggtranstype))
! 			{
! 				int32		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);
  
! 				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
  		{
! 			costs->transitionSpace = work_mem;   /* just in case */
  		}
  
  		/*
***************
*** 3826,3832 **** 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);
  }
  
  /*
--- 3846,3852 ----
  		elog(ERROR, "function's resolved result type changed during planning");
  
  	/* perform any necessary typecasting of arguments */
! 	make_fn_arguments(NULL, args, NULL, actual_arg_types, declared_arg_types, false);
  }
  
  /*
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 951,957 **** transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
  										  &qry->targetList,
  										  EXPR_KIND_ORDER_BY,
  										  true /* fix unknowns */ ,
! 										  false /* allow SQL92 rules */ );
  
  	qry->groupClause = transformGroupClause(pstate,
  											stmt->groupClause,
--- 951,958 ----
  										  &qry->targetList,
  										  EXPR_KIND_ORDER_BY,
  										  true /* fix unknowns */ ,
! 										  false /* allow SQL92 rules */,
! 										  false /* don't add duplicates */);
  
  	qry->groupClause = transformGroupClause(pstate,
  											stmt->groupClause,
***************
*** 1211,1217 **** transformValuesClause(ParseState *pstate, SelectStmt *stmt)
  										  &qry->targetList,
  										  EXPR_KIND_ORDER_BY,
  										  true /* fix unknowns */ ,
! 										  false /* allow SQL92 rules */ );
  
  	qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
  											EXPR_KIND_OFFSET, "OFFSET");
--- 1212,1219 ----
  										  &qry->targetList,
  										  EXPR_KIND_ORDER_BY,
  										  true /* fix unknowns */ ,
! 										  false /* allow SQL92 rules */,
! 										  false /* don't add duplicates */ );
  
  	qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
  											EXPR_KIND_OFFSET, "OFFSET");
***************
*** 1435,1441 **** transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
  										  &qry->targetList,
  										  EXPR_KIND_ORDER_BY,
  										  false /* no unknowns expected */ ,
! 										  false /* allow SQL92 rules */ );
  
  	/* restore namespace, remove jrte from rtable */
  	pstate->p_namespace = sv_namespace;
--- 1437,1444 ----
  										  &qry->targetList,
  										  EXPR_KIND_ORDER_BY,
  										  false /* no unknowns expected */ ,
! 										  false /* allow SQL92 rules */ ,
! 										  false /* don't add duplicates */ );
  
  	/* restore namespace, remove jrte from rtable */
  	pstate->p_namespace = sv_namespace;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 494,499 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
--- 494,500 ----
  %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,602 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
  	VERBOSE VERSION_P VIEW VOLATILE
  
! 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
  
  	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
  	XMLPI XMLROOT XMLSERIALIZE
--- 597,603 ----
  	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
  	VERBOSE VERSION_P VIEW VOLATILE
  
! 	WHEN WHERE WITHIN WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
  
  	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
  	XMLPI XMLROOT XMLSERIALIZE
***************
*** 3660,3666 **** AlterExtensionContentsStmt:
  					n->action = $4;
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $6;
! 					n->objargs = extractArgTypes($7);
  					$$ = (Node *)n;
  				}
  			| ALTER EXTENSION name add_drop CAST '(' Typename AS Typename ')'
--- 3661,3667 ----
  					n->action = $4;
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $6;
! 					n->objargs = extractArgTypes(linitial($7));
  					$$ = (Node *)n;
  				}
  			| ALTER EXTENSION name add_drop CAST '(' Typename AS Typename ')'
***************
*** 5239,5245 **** CommentStmt:
  					CommentStmt *n = makeNode(CommentStmt);
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $4;
! 					n->objargs = extractArgTypes($5);
  					n->comment = $7;
  					$$ = (Node *) n;
  				}
--- 5240,5246 ----
  					CommentStmt *n = makeNode(CommentStmt);
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $4;
! 					n->objargs = extractArgTypes(linitial($5));
  					n->comment = $7;
  					$$ = (Node *) n;
  				}
***************
*** 5405,5411 **** SecLabelStmt:
  					n->provider = $3;
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $6;
! 					n->objargs = extractArgTypes($7);
  					n->label = $9;
  					$$ = (Node *) n;
  				}
--- 5406,5412 ----
  					n->provider = $3;
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $6;
! 					n->objargs = extractArgTypes(linitial($7));
  					n->label = $9;
  					$$ = (Node *) n;
  				}
***************
*** 6405,6413 **** aggr_arg:	func_arg
  				}
  		;
  
! /* Zero-argument aggregates are named with * for consistency with COUNT(*) */
! aggr_args:	'(' aggr_args_list ')'					{ $$ = $2; }
! 			| '(' '*' ')'							{ $$ = NIL; }
  		;
  
  aggr_args_list:
--- 6406,6458 ----
  				}
  		;
  
! /*
!  * 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:
***************
*** 6613,6619 **** RemoveAggrStmt:
  					DropStmt *n = makeNode(DropStmt);
  					n->removeType = OBJECT_AGGREGATE;
  					n->objects = list_make1($3);
! 					n->arguments = list_make1(extractArgTypes($4));
  					n->behavior = $5;
  					n->missing_ok = false;
  					n->concurrent = false;
--- 6658,6664 ----
  					DropStmt *n = makeNode(DropStmt);
  					n->removeType = OBJECT_AGGREGATE;
  					n->objects = list_make1($3);
! 					n->arguments = list_make1(extractArgTypes(linitial($4)));
  					n->behavior = $5;
  					n->missing_ok = false;
  					n->concurrent = false;
***************
*** 6624,6630 **** RemoveAggrStmt:
  					DropStmt *n = makeNode(DropStmt);
  					n->removeType = OBJECT_AGGREGATE;
  					n->objects = list_make1($5);
! 					n->arguments = list_make1(extractArgTypes($6));
  					n->behavior = $7;
  					n->missing_ok = true;
  					n->concurrent = false;
--- 6669,6675 ----
  					DropStmt *n = makeNode(DropStmt);
  					n->removeType = OBJECT_AGGREGATE;
  					n->objects = list_make1($5);
! 					n->arguments = list_make1(extractArgTypes(linitial($6)));
  					n->behavior = $7;
  					n->missing_ok = true;
  					n->concurrent = false;
***************
*** 6840,6846 **** RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
  					RenameStmt *n = makeNode(RenameStmt);
  					n->renameType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes($4);
  					n->newname = $7;
  					n->missing_ok = false;
  					$$ = (Node *)n;
--- 6885,6891 ----
  					RenameStmt *n = makeNode(RenameStmt);
  					n->renameType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes(linitial($4));
  					n->newname = $7;
  					n->missing_ok = false;
  					$$ = (Node *)n;
***************
*** 7314,7320 **** AlterObjectSchemaStmt:
  					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
  					n->objectType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes($4);
  					n->newschema = $7;
  					n->missing_ok = false;
  					$$ = (Node *)n;
--- 7359,7365 ----
  					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
  					n->objectType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes(linitial($4));
  					n->newschema = $7;
  					n->missing_ok = false;
  					$$ = (Node *)n;
***************
*** 7543,7549 **** AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
  					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
  					n->objectType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes($4);
  					n->newowner = $7;
  					$$ = (Node *)n;
  				}
--- 7588,7594 ----
  					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
  					n->objectType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes(linitial($4));
  					n->newowner = $7;
  					$$ = (Node *)n;
  				}
***************
*** 9431,9436 **** sortby:		a_expr USING qual_all_Op opt_nulls_order
--- 9476,9486 ----
  		;
  
  
+ 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); }
***************
*** 11152,11163 **** func_application: func_name '(' ')'
   * (Note that many of the special SQL functions wouldn't actually make any
   * sense as functional index entries, but we ignore that consideration here.)
   */
! func_expr: func_application filter_clause over_clause 
  				{
!               		FuncCall *n = (FuncCall*)$1;
! 					n->agg_filter = $2;
! 					n->over = $3;
! 					$$ = (Node*)n;
  				} 
  			| func_expr_common_subexpr
  				{ $$ = $1; }
--- 11202,11236 ----
   * (Note that many of the special SQL functions wouldn't actually make any
   * sense as functional index entries, but we ignore that consideration here.)
   */
! func_expr: func_application within_group_clause filter_clause over_clause 
  				{
!               		FuncCall *n = (FuncCall *) $1;
! 					/*
! 					 * the order clause for WITHIN GROUP and the one
! 					 * for aggregate ORDER BY share a field, so we
! 					 * have to check here that at most one is present.
! 					 * We check for DISTINCT here to give a better
! 					 * error position.  Other consistency checks are
! 					 * deferred to parse_func.c or parse_agg.c
! 					 */
! 					if ($2 != NIL)
! 					{
! 						if (n->agg_order != NIL)
! 							ereport(ERROR,
! 									(errcode(ERRCODE_SYNTAX_ERROR),
! 									 errmsg("Cannot have multiple ORDER BY clauses with WITHIN GROUP"),
! 									 parser_errposition(@2)));
! 						if (n->agg_distinct)
! 							ereport(ERROR,
! 									(errcode(ERRCODE_SYNTAX_ERROR),
! 									 errmsg("Cannot have DISTINCT and WITHIN GROUP together"),
! 									 parser_errposition(@2)));
! 						n->agg_order = $2;
! 						n->has_within_group = TRUE;
! 					}
! 					n->agg_filter = $3;
! 					n->over = $4;
! 					$$ = (Node *) n;
  				} 
  			| func_expr_common_subexpr
  				{ $$ = $1; }
***************
*** 12716,12721 **** unreserved_keyword:
--- 12789,12795 ----
  			| VIEW
  			| VOLATILE
  			| WHITESPACE_P
+ 			| WITHIN
  			| WITHOUT
  			| WORK
  			| WRAPPER
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
***************
*** 44,50 **** typedef struct
  	int			sublevels_up;
  } check_ungrouped_columns_context;
  
! static int	check_agg_arguments(ParseState *pstate, List *args, Expr *filter);
  static bool check_agg_arguments_walker(Node *node,
  						   check_agg_arguments_context *context);
  static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
--- 44,52 ----
  	int			sublevels_up;
  } check_ungrouped_columns_context;
  
! static int	check_agg_arguments(ParseState *pstate, 
! 							List *args, 
! 							List *agg_ordset, Expr *filter);
  static bool check_agg_arguments_walker(Node *node,
  						   check_agg_arguments_context *context);
  static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
***************
*** 75,81 **** static bool check_ungrouped_columns_walker(Node *node,
   */
  void
  transformAggregateCall(ParseState *pstate, Aggref *agg,
! 					   List *args, List *aggorder, bool agg_distinct)
  {
  	List	   *tlist;
  	List	   *torder;
--- 77,84 ----
   */
  void
  transformAggregateCall(ParseState *pstate, Aggref *agg,
! 					   List *args, List *aggorder,
! 					   bool agg_distinct, bool agg_within_group)
  {
  	List	   *tlist;
  	List	   *torder;
***************
*** 93,104 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
  	 */
  	tlist = NIL;
  	attno = 1;
! 	foreach(lc, args)
  	{
! 		Expr	   *arg = (Expr *) lfirst(lc);
! 		TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false);
  
! 		tlist = lappend(tlist, tle);
  	}
  
  	/*
--- 96,119 ----
  	 */
  	tlist = NIL;
  	attno = 1;
! 
! 	if (agg_within_group)
  	{
! 		agg->isordset = TRUE;
! 		agg->orddirectargs = args;
! 	}
! 	else
! 	{
! 		foreach(lc, args)
! 		{
! 			Expr	   *arg = (Expr *) lfirst(lc);
! 			TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false);
  
! 			tlist = lappend(tlist, tle);
! 		}
! 
! 		agg->isordset = FALSE;
! 		agg->orddirectargs = NIL;
  	}
  
  	/*
***************
*** 109,114 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
--- 124,134 ----
  	 *
  	 * We need to mess with p_next_resno since it will be used to number any
  	 * new targetlist entries.
+ 	 *
+ 	 * If and only if we're doing a WITHIN GROUP list, we preserve any
+ 	 * duplicate expressions in the sort clause. This is needed because the
+ 	 * sort clause of WITHIN GROUP is really an argument list, and we must
+ 	 * keep the number and content of entries matching the specified input.
  	 */
  	save_next_resno = pstate->p_next_resno;
  	pstate->p_next_resno = attno;
***************
*** 118,124 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
  								 &tlist,
  								 EXPR_KIND_ORDER_BY,
  								 true /* fix unknowns */ ,
! 								 true /* force SQL99 rules */ );
  
  	/*
  	 * If we have DISTINCT, transform that to produce a distinctList.
--- 138,145 ----
  								 &tlist,
  								 EXPR_KIND_ORDER_BY,
  								 true /* fix unknowns */ ,
! 								 true /* force SQL99 rules */ ,
! 								 agg_within_group /* keep duplicates? */ );
  
  	/*
  	 * If we have DISTINCT, transform that to produce a distinctList.
***************
*** 160,166 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
  	 * Check the arguments to compute the aggregate's level and detect
  	 * improper nesting.
  	 */
! 	min_varlevel = check_agg_arguments(pstate, agg->args, agg->aggfilter);
  	agg->agglevelsup = min_varlevel;
  
  	/* Mark the correct pstate level as having aggregates */
--- 181,188 ----
  	 * Check the arguments to compute the aggregate's level and detect
  	 * improper nesting.
  	 */
! 	min_varlevel = check_agg_arguments(pstate, 
! 										agg->args, agg->orddirectargs, agg->aggfilter);
  	agg->agglevelsup = min_varlevel;
  
  	/* Mark the correct pstate level as having aggregates */
***************
*** 312,318 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
   * which we can't know until we finish scanning the arguments.
   */
  static int
! check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
  {
  	int			agglevel;
  	check_agg_arguments_context context;
--- 334,340 ----
   * which we can't know until we finish scanning the arguments.
   */
  static int
! check_agg_arguments(ParseState *pstate, List *args, List *agg_ordset, Expr *filter)
  {
  	int			agglevel;
  	check_agg_arguments_context context;
***************
*** 330,335 **** check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
--- 352,360 ----
  								  check_agg_arguments_walker,
  								  (void *) &context);
  
+ 	(void) expression_tree_walker((Node *) agg_ordset,	  check_agg_arguments_walker,
+ 								  (void *) &context);
+ 
  	/*
  	 * If we found no vars nor aggs at all, it's a level-zero aggregate;
  	 * otherwise, its level is the minimum of vars or aggs.
***************
*** 353,360 **** check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
  				(errcode(ERRCODE_GROUPING_ERROR),
  				 errmsg("aggregate function calls cannot be nested"),
  				 parser_errposition(pstate,
! 									locate_agg_of_level((Node *) args,
! 														agglevel))));
  
  	return agglevel;
  }
--- 378,385 ----
  				(errcode(ERRCODE_GROUPING_ERROR),
  				 errmsg("aggregate function calls cannot be nested"),
  				 parser_errposition(pstate,
! 								locate_agg_of_level((Node *) args,
! 								agglevel))));
  
  	return agglevel;
  }
***************
*** 823,830 **** check_ungrouped_columns_walker(Node *node,
  	 * We do need to look at aggregates of lower levels, however.
  	 */
  	if (IsA(node, Aggref) &&
! 		(int) ((Aggref *) node)->agglevelsup >= context->sublevels_up)
  		return false;
  
  	/*
  	 * If we have any GROUP BY items that are not simple Vars, check to see if
--- 848,863 ----
  	 * We do need to look at aggregates of lower levels, however.
  	 */
  	if (IsA(node, Aggref) &&
! 		(int) ((Aggref *) node)->agglevelsup > context->sublevels_up)
! 	{
  		return false;
+ 	}
+ 	else if (IsA(node, Aggref) &&
+ 			(int) ((Aggref *) node)->agglevelsup == context->sublevels_up)
+ 	{
+ 		return check_ungrouped_columns_walker((Node*)(((Aggref *)node)->orddirectargs), 
+ 														context);
+ 	}
  
  	/*
  	 * If we have any GROUP BY items that are not simple Vars, check to see if
***************
*** 1042,1044 **** build_aggregate_fnexprs(Oid *agg_input_types,
--- 1075,1172 ----
  										 agg_input_collation,
  										 COERCE_EXPLICIT_CALL);
  }
+ 
+ void
+ build_orderedset_fnexprs(Oid *agg_input_types,
+ 						 int agg_num_inputs,
+ 						 bool agg_variadic,
+ 						 Oid agg_result_type,
+ 						 Oid agg_input_collation,
+ 						 Oid *agg_input_collation_array,
+ 						 Oid finalfn_oid,
+ 						 Expr **finalfnexpr)
+ {
+ 	FuncExpr   *fexpr;
+ 	Param	   *argp;
+ 	List	   *args = NIL;
+ 	int			i = 0;
+ 
+ 	/*
+ 	 * Build arg list to use in the finalfn FuncExpr node. We really only care
+ 	 * that finalfn can discover the actual argument types at runtime using
+ 	 * get_fn_expr_argtype(), so it's okay to use Param nodes that don't
+ 	 * correspond to any real Param.
+ 	 */
+ 	for (i = 0; i < agg_num_inputs; i++)
+ 	{
+ 		argp = makeNode(Param);
+ 		argp->paramkind = PARAM_EXEC;
+ 		argp->paramid = -1;
+ 		argp->paramtype = agg_input_types[i];
+ 		argp->paramtypmod = -1;
+ 		argp->paramcollid = agg_input_collation_array[i];
+ 		argp->location = -1;
+ 
+ 		args = lappend(args, argp);
+ 	}
+ 
+ 	fexpr = makeFuncExpr(finalfn_oid,
+ 						 agg_result_type,
+ 						 args,
+ 						 InvalidOid,
+ 						 agg_input_collation,
+ 						 COERCE_EXPLICIT_CALL);
+ 	fexpr->funcvariadic = agg_variadic;
+ 	*finalfnexpr = (Expr *) fexpr;
+ }
+ 
+ int get_aggregate_argtypes(Aggref *aggref, Oid *inputTypes, Oid *inputCollations)
+ {
+ 	int numArguments = 0;
+ 	ListCell   *lc;
+ 
+ 	if (!(aggref->isordset))
+ 	{
+ 		foreach(lc, aggref->args)
+ 		{
+ 			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ 
+ 			if (!tle->resjunk)
+ 			{
+ 				inputTypes[numArguments] = exprType((Node *) tle->expr);
+ 				if (inputCollations != NULL)
+ 					inputCollations[numArguments] = exprCollation((Node *) tle->expr);
+ 				++numArguments;
+ 			}
+ 		}
+ 	}
+ 	else
+ 	{
+ 		foreach(lc, aggref->orddirectargs)
+ 		{
+ 			Node *expr_orddirectargs = lfirst(lc);
+ 
+ 			inputTypes[numArguments] = exprType(expr_orddirectargs);
+ 			if (inputCollations != NULL)
+ 				inputCollations[numArguments] = exprCollation(expr_orddirectargs);
+ 
+ 			++numArguments;
+ 		}
+ 
+ 		if (!(aggref->ishypothetical))
+ 		{
+ 			foreach(lc, aggref->args)
+ 			{
+ 				TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ 
+ 				inputTypes[numArguments] = exprType((Node *) tle->expr);
+ 				if (inputCollations != NULL)
+ 					inputCollations[numArguments] = exprCollation((Node *) tle->expr);
+ 
+ 				++numArguments;
+ 			}
+ 		}
+ 	}
+ 
+ 	return numArguments;
+ }
*** a/src/backend/parser/parse_clause.c
--- b/src/backend/parser/parse_clause.c
***************
*** 71,77 **** static void checkExprIsVarFree(ParseState *pstate, Node *n,
  static TargetEntry *findTargetlistEntrySQL92(ParseState *pstate, Node *node,
  						 List **tlist, ParseExprKind exprKind);
  static TargetEntry *findTargetlistEntrySQL99(ParseState *pstate, Node *node,
! 						 List **tlist, ParseExprKind exprKind);
  static int get_matching_location(int sortgroupref,
  					  List *sortgrouprefs, List *exprs);
  static List *addTargetToSortList(ParseState *pstate, TargetEntry *tle,
--- 71,78 ----
  static TargetEntry *findTargetlistEntrySQL92(ParseState *pstate, Node *node,
  						 List **tlist, ParseExprKind exprKind);
  static TargetEntry *findTargetlistEntrySQL99(ParseState *pstate, Node *node,
! 											 List **tlist, ParseExprKind exprKind,
! 											 bool keepDuplicates);
  static int get_matching_location(int sortgroupref,
  					  List *sortgrouprefs, List *exprs);
  static List *addTargetToSortList(ParseState *pstate, TargetEntry *tle,
***************
*** 1476,1482 **** findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
  	/*
  	 * Otherwise, we have an expression, so process it per SQL99 rules.
  	 */
! 	return findTargetlistEntrySQL99(pstate, node, tlist, exprKind);
  }
  
  /*
--- 1477,1483 ----
  	/*
  	 * Otherwise, we have an expression, so process it per SQL99 rules.
  	 */
! 	return findTargetlistEntrySQL99(pstate, node, tlist, exprKind, false);
  }
  
  /*
***************
*** 1491,1500 **** findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
   * node		the ORDER BY, GROUP BY, etc expression to be matched
   * tlist	the target list (passed by reference so we can append to it)
   * exprKind identifies clause type being processed
   */
  static TargetEntry *
  findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
! 						 ParseExprKind exprKind)
  {
  	TargetEntry *target_result;
  	ListCell   *tl;
--- 1492,1502 ----
   * node		the ORDER BY, GROUP BY, etc expression to be matched
   * tlist	the target list (passed by reference so we can append to it)
   * exprKind identifies clause type being processed
+  * keepDuplicates  if true, don't try and match to any existing entry
   */
  static TargetEntry *
  findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
! 						 ParseExprKind exprKind, bool keepDuplicates)
  {
  	TargetEntry *target_result;
  	ListCell   *tl;
***************
*** 1509,1532 **** findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
  	 */
  	expr = transformExpr(pstate, node, exprKind);
  
! 	foreach(tl, *tlist)
  	{
! 		TargetEntry *tle = (TargetEntry *) lfirst(tl);
! 		Node	   *texpr;
  
! 		/*
! 		 * Ignore any implicit cast on the existing tlist expression.
! 		 *
! 		 * This essentially allows the ORDER/GROUP/etc item to adopt the same
! 		 * datatype previously selected for a textually-equivalent tlist item.
! 		 * There can't be any implicit cast at top level in an ordinary SELECT
! 		 * tlist at this stage, but the case does arise with ORDER BY in an
! 		 * aggregate function.
! 		 */
! 		texpr = strip_implicit_coercions((Node *) tle->expr);
  
! 		if (equal(expr, texpr))
! 			return tle;
  	}
  
  	/*
--- 1511,1537 ----
  	 */
  	expr = transformExpr(pstate, node, exprKind);
  
! 	if (!keepDuplicates)
  	{
! 		foreach(tl, *tlist)
! 		{
! 			TargetEntry *tle = (TargetEntry *) lfirst(tl);
! 			Node	   *texpr;
  
! 			/*
! 			 * Ignore any implicit cast on the existing tlist expression.
! 			 *
! 			 * This essentially allows the ORDER/GROUP/etc item to adopt the same
! 			 * datatype previously selected for a textually-equivalent tlist item.
! 			 * There can't be any implicit cast at top level in an ordinary SELECT
! 			 * tlist at this stage, but the case does arise with ORDER BY in an
! 			 * aggregate function.
! 			 */
! 			texpr = strip_implicit_coercions((Node *) tle->expr);
  
! 			if (equal(expr, texpr))
! 				return tle;
! 		}
  	}
  
  	/*
***************
*** 1568,1574 **** transformGroupClause(ParseState *pstate, List *grouplist,
  
  		if (useSQL99)
  			tle = findTargetlistEntrySQL99(pstate, gexpr,
! 										   targetlist, exprKind);
  		else
  			tle = findTargetlistEntrySQL92(pstate, gexpr,
  										   targetlist, exprKind);
--- 1573,1579 ----
  
  		if (useSQL99)
  			tle = findTargetlistEntrySQL99(pstate, gexpr,
! 										   targetlist, exprKind, false);
  		else
  			tle = findTargetlistEntrySQL92(pstate, gexpr,
  										   targetlist, exprKind);
***************
*** 1635,1645 **** transformSortClause(ParseState *pstate,
  					List **targetlist,
  					ParseExprKind exprKind,
  					bool resolveUnknown,
! 					bool useSQL99)
  {
  	List	   *sortlist = NIL;
  	ListCell   *olitem;
  
  	foreach(olitem, orderlist)
  	{
  		SortBy	   *sortby = (SortBy *) lfirst(olitem);
--- 1640,1653 ----
  					List **targetlist,
  					ParseExprKind exprKind,
  					bool resolveUnknown,
! 					bool useSQL99,
! 	                bool keepDuplicates)
  {
  	List	   *sortlist = NIL;
  	ListCell   *olitem;
  
+ 	Assert(useSQL99 || !keepDuplicates);
+ 
  	foreach(olitem, orderlist)
  	{
  		SortBy	   *sortby = (SortBy *) lfirst(olitem);
***************
*** 1647,1653 **** transformSortClause(ParseState *pstate,
  
  		if (useSQL99)
  			tle = findTargetlistEntrySQL99(pstate, sortby->node,
! 										   targetlist, exprKind);
  		else
  			tle = findTargetlistEntrySQL92(pstate, sortby->node,
  										   targetlist, exprKind);
--- 1655,1661 ----
  
  		if (useSQL99)
  			tle = findTargetlistEntrySQL99(pstate, sortby->node,
! 										   targetlist, exprKind, keepDuplicates);
  		else
  			tle = findTargetlistEntrySQL92(pstate, sortby->node,
  										   targetlist, exprKind);
***************
*** 1717,1723 **** transformWindowDefinitions(ParseState *pstate,
  										  targetlist,
  										  EXPR_KIND_WINDOW_ORDER,
  										  true /* fix unknowns */ ,
! 										  true /* force SQL99 rules */ );
  		partitionClause = transformGroupClause(pstate,
  											   windef->partitionClause,
  											   targetlist,
--- 1725,1732 ----
  										  targetlist,
  										  EXPR_KIND_WINDOW_ORDER,
  										  true /* fix unknowns */ ,
! 										  true /* force SQL99 rules */,
! 			                              false /* don't add duplicates */);
  		partitionClause = transformGroupClause(pstate,
  											   windef->partitionClause,
  											   targetlist,
*** a/src/backend/parser/parse_collate.c
--- b/src/backend/parser/parse_collate.c
***************
*** 73,79 **** typedef struct
  static bool assign_query_collations_walker(Node *node, ParseState *pstate);
  static bool assign_collations_walker(Node *node,
  						 assign_collations_context *context);
! 
  
  /*
   * assign_query_collations()
--- 73,81 ----
  static bool assign_query_collations_walker(Node *node, ParseState *pstate);
  static bool assign_collations_walker(Node *node,
  						 assign_collations_context *context);
! static void assign_aggregate_collations(Aggref *aggref,
! 						assign_collations_context *context,
! 						assign_collations_context *loccontext);
  
  /*
   * assign_query_collations()
***************
*** 564,607 **** assign_collations_walker(Node *node, assign_collations_context *context)
  					case T_Aggref:
  						{
  							/*
! 							 * Aggref is a special case because expressions
! 							 * used only for ordering shouldn't be taken to
! 							 * conflict with each other or with regular args.
! 							 * So we apply assign_expr_collations() to them
! 							 * rather than passing down our loccontext.
! 							 *
! 							 * Note that we recurse to each TargetEntry, not
! 							 * directly to its contained expression, so that
! 							 * the case above for T_TargetEntry will apply
! 							 * appropriate checks to agg ORDER BY items.
! 							 *
! 							 * Likewise, we assign collations for the (bool)
! 							 * expression in aggfilter, independently of any
! 							 * other args.
! 							 *
! 							 * We need not recurse into the aggorder or
! 							 * aggdistinct lists, because those contain only
! 							 * SortGroupClause nodes which we need not
! 							 * process.
  							 */
  							Aggref	   *aggref = (Aggref *) node;
- 							ListCell   *lc;
  
! 							foreach(lc, aggref->args)
! 							{
! 								TargetEntry *tle = (TargetEntry *) lfirst(lc);
! 
! 								Assert(IsA(tle, TargetEntry));
! 								if (tle->resjunk)
! 									assign_expr_collations(context->pstate,
! 														   (Node *) tle);
! 								else
! 									(void) assign_collations_walker((Node *) tle,
! 																&loccontext);
! 							}
  
  							assign_expr_collations(context->pstate,
! 												 (Node *) aggref->aggfilter);
  						}
  						break;
  					case T_WindowFunc:
--- 566,581 ----
  					case T_Aggref:
  						{
  							/*
! 							 * Aggref is special enough that we give it its own
! 							 * function. The FILTER clause is independent of the
! 							 * rest of the aggregate, however.
  							 */
  							Aggref	   *aggref = (Aggref *) node;
  
! 							assign_aggregate_collations(aggref, context, &loccontext);
  
  							assign_expr_collations(context->pstate,
! 												   (Node *) aggref->aggfilter);
  						}
  						break;
  					case T_WindowFunc:
***************
*** 802,804 **** assign_collations_walker(Node *node, assign_collations_context *context)
--- 776,934 ----
  
  	return false;
  }
+ 
+ 
+ /*
+  * Aggref is a special case because expressions used only for ordering
+  * shouldn't be taken to conflict with each other or with regular args.  So we
+  * apply assign_expr_collations() to them rather than passing down our
+  * loccontext.
+  *
+  * Note that we recurse to each TargetEntry, not directly to its contained
+  * expression, so that the case above for T_TargetEntry will apply appropriate
+  * checks to agg ORDER BY items.
+  *
+  * We need not recurse into the aggorder or aggdistinct lists, because those
+  * contain only SortGroupClause nodes which we need not process.
+  *
+  * For ordered set functions, it's unfortunately unclear how best to proceed.
+  * The spec-defined inverse distribution functions have only one sort column
+  * and don't allow collatable types, but this is clearly unsatisfactory in the
+  * general case. Compromise by taking the sort column as part of the collation
+  * determination if, and only if, there is only one such column, and force the
+  * final choice of input collation down into the sort column if need be; but
+  * don't error out unless actually necessary (leaving it up to the function to
+  * handle the issue at runtime). This ugly wart is justified by the fact that
+  * there seems to be no other good way to get a result collation for
+  * percentile_* applied to a collatable type.
+  *
+  * But hypothetical set functions are special; they must have
+  * pairwise-assigned collations for each matching pair of args, and again we
+  * need to force the final choice of collation down into the sort column to
+  * ensure that the sort happens on the chosen collation. If there are any
+  * additional args (not allowed in the spec, but a user-defined function might
+  * have some), those contribute to the result collation in the normal way.
+  * (The hypothetical paired args never contribute to the result collation at
+  * all.)
+  */
+ 
+ static Expr *
+ relabel_expr_collation(Expr *expr, Oid newcollation)
+ {
+ 	RelabelType *node = makeNode(RelabelType);
+ 	node->arg = expr;
+ 	node->resulttype = exprType((Node *)expr);
+ 	node->resulttypmod = exprTypmod((Node *)expr);
+ 	node->resultcollid = newcollation;
+ 	node->relabelformat = COERCE_IMPLICIT_CAST;
+ 	node->location = exprLocation((Node *)expr);
+ 	return (Expr *) node;
+ }
+ 
+ static void
+ assign_aggregate_collations(Aggref *aggref,
+ 							assign_collations_context *context,
+ 							assign_collations_context *loccontext)
+ {
+ 	ListCell   *lc;
+ 
+ 	if (aggref->ishypothetical)
+ 	{
+ 		/*-
+ 		 * Hypothetical set function, i.e.
+ 		 *   func(..., a,b,c,...) within group (p,q,r,...)
+ 		 *
+ 		 * Any initial set of direct args (before "a") contributes to the
+ 		 * result collation in the usual way for function args. But none of
+ 		 * a,b,c... or p,q,r... contribute at all; instead, they must be
+ 		 * paired up (as though UNIONed) and the sorted col's collation forced
+ 		 * to the chosen value (so that we sort it correctly).
+ 		 */
+ 		int initial_args = list_length(aggref->orddirectargs) - list_length(aggref->args);
+ 		ListCell *h_arg = list_head(aggref->orddirectargs);
+ 		ListCell *s_arg = list_head(aggref->args);
+ 
+ 		Assert(initial_args >= 0);
+ 
+ 		while (initial_args-- > 0)
+ 		{
+ 			(void) assign_collations_walker((Node *) lfirst(h_arg), loccontext);
+ 			h_arg = lnext(h_arg);
+ 		}
+ 
+ 		for_each_cell(h_arg,h_arg)
+ 		{
+ 			TargetEntry *tle = (TargetEntry *) lfirst(s_arg);
+ 			Oid coll = select_common_collation(context->pstate,
+ 											   list_make2(lfirst(h_arg),lfirst(s_arg)),
+ 											   false);
+ 
+ 			/*
+ 			 * we can only get InvalidOid here if the type is not collatable,
+ 			 * so no need to try and relabel in that case.
+ 			 */
+ 
+ 			if (OidIsValid(coll)
+ 				&& coll != exprCollation((Node *)(tle->expr)))
+ 			{
+ 				tle->expr = relabel_expr_collation(tle->expr, coll);
+ 			}
+ 
+ 			s_arg = lnext(s_arg);
+ 		}
+ 	}
+ 	else if (aggref->isordset && list_length(aggref->args) == 1)
+ 	{
+ 		/*
+ 		 * Ordered set func with one sorted arg
+ 		 */
+ 		TargetEntry *tle = (TargetEntry *) linitial(aggref->args);
+ 
+ 		/* do the TLE first so that it won't error out on conflicts */
+ 
+ 		(void) assign_collations_walker((Node *) tle,
+ 										loccontext);
+ 
+ 		(void) assign_collations_walker((Node *) aggref->orddirectargs,
+ 										loccontext);
+ 
+ 		/*
+ 		 * If the sort col is a collatable type, and we chose a collation,
+ 		 * and it's not the one the sort col already has, then force the
+ 		 * sort col's collation (which can't have been explicit) to the
+ 		 * chosen one. Otherwise leave it alone.
+ 		 */
+ 		if (type_is_collatable(exprType((Node *)(tle->expr)))
+ 			&& (loccontext->strength == COLLATE_IMPLICIT
+ 				|| loccontext->strength == COLLATE_EXPLICIT)
+ 			&& exprCollation((Node *)(tle->expr)) != loccontext->collation)
+ 		{
+ 			tle->expr = relabel_expr_collation(tle->expr, loccontext->collation);
+ 		}
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * For this case, we do the direct args (if any) together, as is
+ 		 * normal for functions, but args which are either used only for
+ 		 * sorting or are only part of a WITHIN GROUP are processed
+ 		 * individually.
+ 		 */
+ 
+ 		(void) assign_collations_walker((Node *) aggref->orddirectargs,
+ 										loccontext);
+ 
+ 		foreach(lc, aggref->args)
+ 		{
+ 			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ 
+ 			Assert(IsA(tle, TargetEntry));
+ 			if (tle->resjunk)
+ 				assign_expr_collations(context->pstate,
+ 									   (Node *) tle);
+ 			else
+ 				(void) assign_collations_walker((Node *) tle,
+ 												loccontext);
+ 		}
+ 	}
+ }
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
***************
*** 463,470 **** transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
  			newresult = ParseFuncOrColumn(pstate,
  										  list_make1(n),
  										  list_make1(result),
! 										  NIL, NULL, false, false, false,
! 										  NULL, true, location);
  			if (newresult == NULL)
  				unknown_attribute(pstate, result, strVal(n), location);
  			result = newresult;
--- 463,470 ----
  			newresult = ParseFuncOrColumn(pstate,
  										  list_make1(n),
  										  list_make1(result),
! 										  location,
! 										  NULL);
  			if (newresult == NULL)
  				unknown_attribute(pstate, result, strVal(n), location);
  			result = newresult;
***************
*** 631,638 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
  					node = ParseFuncOrColumn(pstate,
  											 list_make1(makeString(colname)),
  											 list_make1(node),
! 											 NIL, NULL, false, false, false,
! 											 NULL, true, cref->location);
  				}
  				break;
  			}
--- 631,637 ----
  					node = ParseFuncOrColumn(pstate,
  											 list_make1(makeString(colname)),
  											 list_make1(node),
! 											 cref->location, NULL);
  				}
  				break;
  			}
***************
*** 676,683 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
  					node = ParseFuncOrColumn(pstate,
  											 list_make1(makeString(colname)),
  											 list_make1(node),
! 											 NIL, NULL, false, false, false,
! 											 NULL, true, cref->location);
  				}
  				break;
  			}
--- 675,681 ----
  					node = ParseFuncOrColumn(pstate,
  											 list_make1(makeString(colname)),
  											 list_make1(node),
! 											 cref->location, NULL);
  				}
  				break;
  			}
***************
*** 734,741 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
  					node = ParseFuncOrColumn(pstate,
  											 list_make1(makeString(colname)),
  											 list_make1(node),
! 											 NIL, NULL, false, false, false,
! 											 NULL, true, cref->location);
  				}
  				break;
  			}
--- 732,738 ----
  					node = ParseFuncOrColumn(pstate,
  											 list_make1(makeString(colname)),
  											 list_make1(node),
! 											 cref->location, NULL);
  				}
  				break;
  			}
***************
*** 1242,1279 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
  {
  	List	   *targs;
  	ListCell   *args;
- 	Expr	   *tagg_filter;
  
  	/* Transform the list of arguments ... */
  	targs = NIL;
  	foreach(args, fn->args)
  	{
! 		targs = lappend(targs, transformExprRecurse(pstate,
! 													(Node *) lfirst(args)));
  	}
  
- 	/*
- 	 * Transform the aggregate filter using transformWhereClause(), to which
- 	 * FILTER is virtually identical...
- 	 */
- 	tagg_filter = NULL;
- 	if (fn->agg_filter != NULL)
- 		tagg_filter = (Expr *)
- 			transformWhereClause(pstate, (Node *) fn->agg_filter,
- 								 EXPR_KIND_FILTER, "FILTER");
- 
  	/* ... and hand off to ParseFuncOrColumn */
  	return ParseFuncOrColumn(pstate,
  							 fn->funcname,
  							 targs,
! 							 fn->agg_order,
! 							 tagg_filter,
! 							 fn->agg_star,
! 							 fn->agg_distinct,
! 							 fn->func_variadic,
! 							 fn->over,
! 							 false,
! 							 fn->location);
  }
  
  static Node *
--- 1239,1258 ----
  {
  	List	   *targs;
  	ListCell   *args;
  
  	/* Transform the list of arguments ... */
  	targs = NIL;
  	foreach(args, fn->args)
  	{
! 		targs = lappend(targs, transformExprRecurse(pstate, (Node *) lfirst(args)));
  	}
  
  	/* ... and hand off to ParseFuncOrColumn */
  	return ParseFuncOrColumn(pstate,
  							 fn->funcname,
  							 targs,
! 							 fn->location,
! 							 fn);
  }
  
  static Node *
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
***************
*** 17,32 ****
--- 17,35 ----
  #include "access/htup_details.h"
  #include "catalog/pg_proc.h"
  #include "catalog/pg_type.h"
+ #include "catalog/pg_aggregate.h"
  #include "funcapi.h"
  #include "lib/stringinfo.h"
  #include "nodes/makefuncs.h"
  #include "nodes/nodeFuncs.h"
  #include "parser/parse_agg.h"
+ #include "parser/parse_clause.h"
  #include "parser/parse_coerce.h"
  #include "parser/parse_func.h"
  #include "parser/parse_relation.h"
  #include "parser/parse_target.h"
  #include "parser/parse_type.h"
+ #include "parser/parse_expr.h"
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
  #include "utils/syscache.h"
***************
*** 56,70 **** static Node *ParseComplexProjection(ParseState *pstate, char *funcname,
   *	Also, when is_column is true, we return NULL on failure rather than
   *	reporting a no-such-function error.
   *
!  *	The argument expressions (in fargs) and filter must have been transformed
!  *	already.  But the agg_order expressions, if any, have not been.
   */
  Node *
  ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
! 				  List *agg_order, Expr *agg_filter,
! 				  bool agg_star, bool agg_distinct, bool func_variadic,
! 				  WindowDef *over, bool is_column, int location)
  {
  	Oid			rettype;
  	Oid			funcid;
  	ListCell   *l;
--- 59,79 ----
   *	Also, when is_column is true, we return NULL on failure rather than
   *	reporting a no-such-function error.
   *
!  *	The argument expressions (in fargs) must have been transformed
!  *	already.
   */
  Node *
  ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
! 				  int location, FuncCall *fn)
  {
+ 	List       *agg_order = (fn ? fn->agg_order : NIL);
+ 	Expr       *agg_filter = NULL;
+ 	bool        agg_star = (fn ? fn->agg_star : false);
+ 	bool        agg_distinct = (fn ? fn->agg_distinct : false);
+ 	bool        agg_within_group = (fn ? fn->has_within_group : false);
+ 	bool        func_variadic = (fn ? fn->func_variadic : false);
+ 	WindowDef  *over = (fn ? fn->over : NULL);
+ 	bool        is_column = (fn == NULL);
  	Oid			rettype;
  	Oid			funcid;
  	ListCell   *l;
***************
*** 81,86 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 90,101 ----
  	int			nvargs;
  	Oid			vatype;
  	FuncDetailCode fdresult;
+ 	int		number_of_args = -1;
+ 	bool		isordsetfunc = false;
+ 	bool		ishypotheticalsetfunc = false;
+ 
+ 	/* Check if the function has WITHIN GROUP as well as distinct. */
+ 	Assert(!(agg_within_group && agg_distinct));
  
  	/*
  	 * Most of the rest of the parser just assumes that functions do not have
***************
*** 98,103 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 113,127 ----
  				 parser_errposition(pstate, location)));
  
  	/*
+ 	 * Transform the aggregate filter using transformWhereClause(), to which
+ 	 * FILTER is virtually identical...
+ 	 */
+ 	if (fn && fn->agg_filter != NULL)
+ 		agg_filter = (Expr *)
+ 			transformWhereClause(pstate, (Node *) fn->agg_filter,
+ 								 EXPR_KIND_FILTER, "FILTER");
+ 
+ 	/*
  	 * Extract arg type info in preparation for function lookup.
  	 *
  	 * If any arguments are Param markers of type VOID, we discard them from
***************
*** 163,168 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 187,198 ----
  		}
  	}
  
+ 	if (agg_within_group && argnames)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("ordered set functions cannot use named arguments"),
+ 				 parser_errposition(pstate, location)));
+ 
  	if (fargs)
  	{
  		first_arg = linitial(fargs);
***************
*** 170,175 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 200,225 ----
  	}
  
  	/*
+ 	 * If WITHIN GROUP is present, we need to call transformExpr on each
+ 	 * SortBy node in agg_order, then call exprType and append to
+ 	 * actual_arg_types, in order to get the types of values in WITHIN GROUP
+ 	 * clause.
+ 	 */
+ 	if (agg_within_group)
+ 	{
+ 		Assert(agg_order != NIL);
+ 
+ 		foreach(l, agg_order)
+ 		{
+ 			SortBy	   *arg = (SortBy *) lfirst(l);
+ 
+ 			arg->node = transformExpr(pstate, arg->node, EXPR_KIND_ORDER_BY);
+ 
+ 			actual_arg_types[nargs++] = exprType(arg->node);
+ 		}
+ 	}
+ 
+ 	/*
  	 * Check for column projection: if function has one argument, and that
  	 * argument is of complex type, and function name is not qualified, then
  	 * the "function call" could be a projection.  We also check that there
***************
*** 247,252 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 297,308 ----
  			errmsg("DISTINCT specified, but %s is not an aggregate function",
  				   NameListToString(funcname)),
  					 parser_errposition(pstate, location)));
+ 		if (agg_within_group)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 			errmsg("WITHIN GROUP specified, but %s is not an ordered set function",
+ 				   NameListToString(funcname)),
+ 					 parser_errposition(pstate, location)));
  		if (agg_order != NIL)
  			ereport(ERROR,
  					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 266,271 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 322,373 ----
  							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("Incorrect number of arguments for hypothetical set function"),
+ 							 parser_errposition(pstate, location)));
+ 			}
+ 			else
+ 			{
+ 				number_of_args = classForm->aggordnargs;
+ 			}
+ 
+ 			if (!agg_within_group)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 						 errmsg("WITHIN GROUP is required for call to ordered set function %s",
+ 								NameListToString(funcname)),
+ 						 parser_errposition(pstate, location)));
+ 		
+ 			if (over)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("OVER clause not supported for call to ordered set function %s",
+ 								NameListToString(funcname)),
+ 						 parser_errposition(pstate, location)));
+ 		}
+ 
+ 		ReleaseSysCache(tup);
+ 	}
  	else if (!(fdresult == FUNCDETAIL_AGGREGATE ||
  			   fdresult == FUNCDETAIL_WINDOWFUNC))
  	{
***************
*** 351,363 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
  											   false);
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, fargs, actual_arg_types, declared_arg_types);
  
  	/*
  	 * If it's a variadic function call, transform the last nvargs arguments
  	 * into an array --- unless it's an "any" variadic.
  	 */
! 	if (nvargs > 0 && declared_arg_types[nargs - 1] != ANYOID)
  	{
  		ArrayExpr  *newa = makeNode(ArrayExpr);
  		int			non_var_args = nargs - nvargs;
--- 453,468 ----
  											   false);
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, fargs, (isordsetfunc) ? agg_order : NIL, 
! 					  actual_arg_types, 
! 					  declared_arg_types,
! 					  ishypotheticalsetfunc);
  
  	/*
  	 * If it's a variadic function call, transform the last nvargs arguments
  	 * into an array --- unless it's an "any" variadic.
  	 */
! 	if (nvargs > 0 && vatype != ANYOID)
  	{
  		ArrayExpr  *newa = makeNode(ArrayExpr);
  		int			non_var_args = nargs - nvargs;
***************
*** 388,403 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
  	 * When function is called with an explicit VARIADIC labeled parameter,
  	 * and the declared_arg_type is "any", then sanity check the actual
  	 * parameter type now - it must be an array.
  	 */
  	if (nargs > 0 && vatype == ANYOID && func_variadic)
  	{
! 		Oid		va_arr_typid = actual_arg_types[nargs - 1];
  
  		if (!OidIsValid(get_element_type(va_arr_typid)))
  			ereport(ERROR,
  					(errcode(ERRCODE_DATATYPE_MISMATCH),
  					 errmsg("VARIADIC argument must be an array"),
! 			  parser_errposition(pstate, exprLocation((Node *) llast(fargs)))));
  	}
  
  	/* build the appropriate output structure */
--- 493,523 ----
  	 * When function is called with an explicit VARIADIC labeled parameter,
  	 * and the declared_arg_type is "any", then sanity check the actual
  	 * parameter type now - it must be an array.
+ 	 *
+ 	 * Also, it can't be a hypothetical set function, and if it's an ordered
+ 	 * set function, the variadic labeled parameter is the last _direct_ arg,
+ 	 * not an ordered arg. (In practice we're unlikely to get this far for
+ 	 * hypotheticals, since make_fn_arguments would probably fail to unify
+ 	 * types, but we can't change the order of these.)
  	 */
  	if (nargs > 0 && vatype == ANYOID && func_variadic)
  	{
! 		int     ignore_args = (agg_within_group ? list_length(agg_order) : 0);
! 		Oid		va_arr_typid = actual_arg_types[nargs - 1 - ignore_args];
! 
! 		if (ishypotheticalsetfunc)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_SYNTAX_ERROR),
! 			  errmsg("explicit VARIADIC argument not allowed for hypothetical set function"),
! 			  parser_errposition(pstate,
! 								 exprLocation((Node *) list_nth(fargs, nargs - 1 - ignore_args)))));
  
  		if (!OidIsValid(get_element_type(va_arr_typid)))
  			ereport(ERROR,
  					(errcode(ERRCODE_DATATYPE_MISMATCH),
  					 errmsg("VARIADIC argument must be an array"),
! 			  parser_errposition(pstate,
! 								 exprLocation((Node *) list_nth(fargs, nargs - 1 - ignore_args)))));
  	}
  
  	/* build the appropriate output structure */
***************
*** 421,426 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 541,552 ----
  		/* aggregate function */
  		Aggref	   *aggref = makeNode(Aggref);
  
+ 		if (agg_within_group && !isordsetfunc)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 					 errmsg("%s is not an ordered set function",
+ 							func_signature_string(funcname, nargs, NIL, actual_arg_types))));
+ 
  		aggref->aggfnoid = funcid;
  		aggref->aggtype = rettype;
  		/* aggcollid and inputcollid will be set by parse_collate.c */
***************
*** 428,441 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
  		aggref->aggfilter = agg_filter;
  		aggref->aggstar = agg_star;
  		aggref->aggvariadic = func_variadic;
  		/* agglevelsup will be set by transformAggregateCall */
  		aggref->location = location;
  
  		/*
  		 * Reject attempt to call a parameterless aggregate without (*)
  		 * syntax.	This is mere pedantry but some folks insisted ...
  		 */
! 		if (fargs == NIL && !agg_star)
  			ereport(ERROR,
  					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
  					 errmsg("%s(*) must be used to call a parameterless aggregate function",
--- 554,577 ----
  		aggref->aggfilter = agg_filter;
  		aggref->aggstar = agg_star;
  		aggref->aggvariadic = func_variadic;
+ 		aggref->ishypothetical = ishypotheticalsetfunc;
  		/* agglevelsup will be set by transformAggregateCall */
  		aggref->location = location;
  
+ 		if (isordsetfunc
+ 			&& number_of_args >= 0
+ 			&& number_of_args != list_length(fargs))
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 					 errmsg("Incorrect number of direct arguments to ordered set function %s",
+ 							NameListToString(funcname)),
+ 					 parser_errposition(pstate, location)));
+ 
  		/*
  		 * Reject attempt to call a parameterless aggregate without (*)
  		 * syntax.	This is mere pedantry but some folks insisted ...
  		 */
! 		if (fargs == NIL && !agg_star && !agg_within_group)
  			ereport(ERROR,
  					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
  					 errmsg("%s(*) must be used to call a parameterless aggregate function",
***************
*** 464,470 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
  					 parser_errposition(pstate, location)));
  
  		/* parse_agg.c does additional aggregate-specific processing */
! 		transformAggregateCall(pstate, aggref, fargs, agg_order, agg_distinct);
  
  		retval = (Node *) aggref;
  	}
--- 600,607 ----
  					 parser_errposition(pstate, location)));
  
  		/* parse_agg.c does additional aggregate-specific processing */
! 		transformAggregateCall(pstate, aggref, fargs, agg_order,
! 							   agg_distinct, agg_within_group);
  
  		retval = (Node *) aggref;
  	}
***************
*** 473,478 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 610,621 ----
  		/* 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,1384 **** func_get_detail(List *funcname,
  void
  make_fn_arguments(ParseState *pstate,
  				  List *fargs,
  				  Oid *actual_arg_types,
! 				  Oid *declared_arg_types)
  {
  	ListCell   *current_fargs;
  	int			i = 0;
  
  	foreach(current_fargs, fargs)
  	{
--- 1517,1537 ----
  void
  make_fn_arguments(ParseState *pstate,
  				  List *fargs,
+ 				  List *agg_order,
  				  Oid *actual_arg_types,
! 				  Oid *declared_arg_types,
! 				  bool requiresUnification)
  {
  	ListCell   *current_fargs;
+ 	ListCell   *current_aoargs;
  	int			i = 0;
+ 	int         unify_offset = -1;
+ 
+ 	if (requiresUnification)
+ 	{
+ 		unify_offset = list_length(fargs) - list_length(agg_order);
+ 		Assert(unify_offset >= 0);
+ 	}
  
  	foreach(current_fargs, fargs)
  	{
***************
*** 1386,1391 **** make_fn_arguments(ParseState *pstate,
--- 1539,1545 ----
  		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,1423 **** make_fn_arguments(ParseState *pstate,
  			}
  			else
  			{
! 				node = coerce_type(pstate,
! 								   node,
! 								   actual_arg_types[i],
! 								   declared_arg_types[i], -1,
! 								   COERCION_IMPLICIT,
! 								   COERCE_IMPLICIT_CAST,
! 								   -1);
! 				lfirst(current_fargs) = node;
  			}
  		}
  		i++;
  	}
  }
  
  /*
--- 1560,1625 ----
  			}
  			else
  			{
! 				/* 
! 				 * If we are dealing with a hypothetical set function, we
! 				 * need to unify agg_order and fargs.
! 				 */
! 
! 				if (declared_arg_types[i] == ANYOID && requiresUnification)
! 				{
! 					Oid unification_oid;
! 					SortBy *unify_with = (SortBy *) list_nth(agg_order,i - unify_offset);
! 
! 					unification_oid = select_common_type(pstate,
! 														 list_make2(unify_with->node,node),
! 														 "WITHIN GROUP",
! 														 NULL);
! 
! 					declared_arg_types[i + list_length(agg_order)] = unification_oid;
! 
! 					temp = coerce_type(pstate,
! 									   node,
! 									   actual_arg_types[i],
! 									   unification_oid, -1,
! 									   COERCION_IMPLICIT,
! 									   COERCE_IMPLICIT_CAST,
! 									   -1);
! 				}
! 				else
! 				{
! 					temp = coerce_type(pstate,
! 									   node,
! 									   actual_arg_types[i],
! 									   declared_arg_types[i], -1,
! 									   COERCION_IMPLICIT,
! 									   COERCE_IMPLICIT_CAST,
! 									   -1);
! 				}
! 
! 				lfirst(current_fargs) = temp;
  			}
  		}
  		i++;
  	}
+ 
+ 	foreach(current_aoargs, agg_order)
+ 	{
+ 		if (actual_arg_types[i] != declared_arg_types[i])
+ 		{
+ 			SortBy	   *node = (SortBy *) lfirst(current_aoargs);
+ 			Node       *temp = NULL;
+ 
+ 			temp = coerce_type(pstate,
+ 							   node->node,
+ 							   actual_arg_types[i],
+ 							   declared_arg_types[i], -1,
+ 							   COERCION_IMPLICIT,
+ 							   COERCE_IMPLICIT_CAST,
+ 							   -1);
+ 			node->node = temp;
+ 		}
+ 		i++;
+ 	}
  }
  
  /*
*** a/src/backend/parser/parse_oper.c
--- b/src/backend/parser/parse_oper.c
***************
*** 823,829 **** make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
  											   false);
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
  
  	/* and build the expression node */
  	result = makeNode(OpExpr);
--- 823,829 ----
  											   false);
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types, false);
  
  	/* and build the expression node */
  	result = makeNode(OpExpr);
***************
*** 953,959 **** make_scalar_array_op(ParseState *pstate, List *opname,
  	declared_arg_types[1] = res_atypeId;
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
  
  	/* and build the expression node */
  	result = makeNode(ScalarArrayOpExpr);
--- 953,959 ----
  	declared_arg_types[1] = res_atypeId;
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types, false);
  
  	/* and build the expression node */
  	result = makeNode(ScalarArrayOpExpr);
*** a/src/backend/utils/adt/Makefile
--- b/src/backend/utils/adt/Makefile
***************
*** 19,31 **** OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
  	array_userfuncs.o arrayutils.o bool.o \
  	cash.o char.o date.o datetime.o datum.o domains.o \
  	enum.o float.o format_type.o \
! 	geo_ops.o geo_selfuncs.o int.o int8.o json.o jsonfuncs.o like.o \
  	lockfuncs.o misc.o nabstime.o name.o numeric.o numutils.o \
  	oid.o oracle_compat.o pseudotypes.o rangetypes.o rangetypes_gist.o \
  	rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \
  	tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \
  	network.o mac.o inet_cidr_ntop.o inet_net_pton.o \
! 	ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \
  	ascii.o quote.o pgstatfuncs.o encode.o dbsize.o genfile.o trigfuncs.o \
  	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
  	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
--- 19,31 ----
  	array_userfuncs.o arrayutils.o bool.o \
  	cash.o char.o date.o datetime.o datum.o domains.o \
  	enum.o float.o format_type.o \
! 	geo_ops.o geo_selfuncs.o hypotheticalset.o int.o int8.o json.o jsonfuncs.o like.o \
  	lockfuncs.o misc.o nabstime.o name.o numeric.o numutils.o \
  	oid.o oracle_compat.o pseudotypes.o rangetypes.o rangetypes_gist.o \
  	rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \
  	tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \
  	network.o mac.o inet_cidr_ntop.o inet_net_pton.o \
! 	inversedistribution.o ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \
  	ascii.o quote.o pgstatfuncs.o encode.o dbsize.o genfile.o trigfuncs.o \
  	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
  	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
*** /dev/null
--- b/src/backend/utils/adt/hypotheticalset.c
***************
*** 0 ****
--- 1,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);
+ }
*** /dev/null
--- b/src/backend/utils/adt/inversedistribution.c
***************
*** 0 ****
--- 1,662 ----
+ /*-------------------------------------------------------------------------
+  *
+  * inversedistribution.c
+  *	  Inverse distribution functions.
+  *
+  * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  *
+  * IDENTIFICATION
+  *	  src/backend/utils/adt/inversedistribution.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ #include "postgres.h"
+ #include "fmgr.h"
+ #include <string.h>
+ #include <math.h>
+ 
+ #include "utils/tuplesort.h"
+ #include "catalog/pg_type.h"
+ #include "utils/datetime.h"
+ #include "utils/lsyscache.h"
+ #include "utils/array.h"
+ 
+ /*
+  * percentile_disc(float8)  - discrete percentile
+  */
+ 
+ Datum percentile_disc_final(PG_FUNCTION_ARGS);
+ 
+ Datum
+ percentile_disc_final(PG_FUNCTION_ARGS)
+ {
+ 	float8		percentile;
+ 	Tuplesortstate *sorter;
+ 	Oid			datumtype;
+ 	Datum		val;
+ 	bool		isnull;
+ 	int64		skiprows;
+ 	int64		rowcount   = AggSetGetRowCount(fcinfo);
+ 
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	percentile = PG_GETARG_FLOAT8(0);
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ 
+ 	if (percentile < 0 || percentile > 1 || isnan(percentile))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("percentile value %g must be between 0 and 1", percentile)));
+ 
+ 	if (rowcount < 1)
+ 		PG_RETURN_NULL();
+ 
+ 	tuplesort_performsort(sorter);
+ 
+ 	/*
+ 	 * We need the smallest K such that (K/N) >= percentile. K starts at 1.
+ 	 * Therefore K >= N*percentile
+ 	 * Therefore K = ceil(N*percentile)
+ 	 * So we skip K-1 rows (if K>0) and return the next row fetched.
+ 	 *
+ 	 * We don't actually expect to see nulls in the input, our strict flag
+ 	 * should have filtered them out, but we're required to not crash if
+ 	 * there is one.
+ 	 */
+ 
+ 	skiprows = (int64) ceil(percentile * rowcount);
+ 	Assert(skiprows <= rowcount);
+ 
+ 	while (--skiprows > 0)
+ 		if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ 			elog(ERROR,"missing row in percentile_disc");
+ 
+ 	if (!tuplesort_getdatum(sorter, true, &val, &isnull))
+ 		elog(ERROR,"missing row in percentile_disc");
+ 
+ 	if (isnull)
+ 		PG_RETURN_NULL();
+ 	else
+ 		PG_RETURN_DATUM(val);
+ }
+ 
+ 
+ /*
+  * For percentile_cont, we need a way to interpolate between consecutive
+  * values. Use a helper function for that, so that we can share the rest
+  * of the code between types.
+  */
+ 
+ static Datum float8_lerp(Datum lo, Datum hi, float8 pct)
+ {
+ 	float8 loval = DatumGetFloat8(lo);
+ 	float8 hival = DatumGetFloat8(hi); 
+ 	return Float8GetDatum(loval + (pct * (hival - loval)));
+ }
+ 
+ static Datum interval_lerp(Datum lo, Datum hi, float8 pct)
+ {
+ 	Datum diff_result = DirectFunctionCall2(interval_mi, hi, lo);
+ 	Datum mul_result  = DirectFunctionCall2(interval_mul,
+ 											diff_result,
+ 											Float8GetDatumFast(pct));
+ 	return DirectFunctionCall2(interval_pl, mul_result, lo);
+ }
+ 
+ typedef Datum (*LerpFunc)(Datum lo, Datum hi, float8 pct);
+ 
+ static Datum
+ percentile_cont_final_common(FunctionCallInfo fcinfo,
+ 							 Oid expect_type,
+ 							 LerpFunc lerpfunc)
+ {
+ 	float8		percentile;
+ 	int64		rowcount = AggSetGetRowCount(fcinfo);
+ 	Tuplesortstate *sorter;
+ 	Oid			datumtype;
+ 	Datum		val;
+ 	Datum		first_row;
+ 	Datum		second_row;
+ 	float8		proportion;
+ 	bool		isnull;
+ 	int64		skiprows;
+ 	int64		lower_row = 0;
+ 	int64		higher_row = 0;
+ 
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	percentile = PG_GETARG_FLOAT8(0);
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ 
+ 	Assert(datumtype == expect_type);
+ 
+ 	if (percentile < 0 || percentile > 1 || isnan(percentile))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("percentile value %g must be between 0 and 1", percentile)));
+ 
+ 	if (rowcount < 1)
+ 		PG_RETURN_NULL();
+ 
+ 	tuplesort_performsort(sorter);
+ 
+ 	lower_row = floor(percentile * (rowcount - 1));
+ 	higher_row = ceil(percentile * (rowcount - 1));
+ 
+ 	Assert(lower_row < rowcount);
+ 
+ 	for (skiprows = lower_row; skiprows > 0; --skiprows)
+ 		if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ 			elog(ERROR,"missing row in percentile_cont");
+ 
+ 	if (!tuplesort_getdatum(sorter, true, &first_row, &isnull))
+ 		elog(ERROR,"missing row in percentile_cont");
+ 	if (isnull)
+ 		PG_RETURN_NULL();
+ 
+ 	if (lower_row == higher_row)
+ 	{
+ 		val = first_row;
+ 	}
+ 	else
+ 	{
+ 		if (!tuplesort_getdatum(sorter, true, &second_row, &isnull))
+ 			elog(ERROR,"missing row in percentile_cont");
+ 
+ 		if (isnull)
+ 			PG_RETURN_NULL();
+ 
+ 		proportion = (percentile * (rowcount-1)) - lower_row;
+ 		val = lerpfunc(first_row, second_row, proportion);
+ 	}
+ 
+ 	if (isnull)
+ 		PG_RETURN_NULL();
+ 	else
+ 		PG_RETURN_DATUM(val);
+ }
+ 
+ 
+ 
+ /*
+  * percentile_cont(float8)  - continuous percentile
+  */
+ 
+ Datum percentile_cont_float8_final(PG_FUNCTION_ARGS);
+ Datum percentile_cont_interval_final(PG_FUNCTION_ARGS);
+ 
+ Datum
+ percentile_cont_float8_final(PG_FUNCTION_ARGS)
+ {
+ 	return percentile_cont_final_common(fcinfo, FLOAT8OID, float8_lerp);
+ }
+ 
+ /*
+  * percentile_interval_cont(Interval)  - continuous percentile for Interval
+  */
+ 
+ Datum
+ percentile_cont_interval_final(PG_FUNCTION_ARGS)
+ {
+ 	return percentile_cont_final_common(fcinfo, INTERVALOID, interval_lerp);
+ }
+ 
+ 
+ /*
+  * mode() - most common value
+  */
+ 
+ Datum mode_final(PG_FUNCTION_ARGS);
+ 
+ Datum
+ mode_final(PG_FUNCTION_ARGS)
+ {
+ 	Tuplesortstate *sorter;
+ 	Oid			datumtype;
+ 	bool		isnull;
+ 	Datum		val;
+ 	Datum		last_val;
+ 	bool		last_val_is_mode = false;
+ 	int64		val_freq = 0;
+ 	Datum		mode_val;
+ 	int64		mode_freq = 0;
+ 	FmgrInfo   *equalfn;
+ 	bool		shouldfree;
+ 
+ 	struct mode_type_info {
+ 		Oid typid;
+ 		int16 typLen;
+ 		bool typByVal;
+ 	} *typinfo = fcinfo->flinfo->fn_extra;
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ 	AggSetGetDistinctInfo(fcinfo, NULL, NULL, &equalfn);
+ 
+ 	if (!typinfo || typinfo->typid != datumtype)
+ 	{
+ 		if (typinfo)
+ 			pfree(typinfo);
+ 		typinfo = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 									 sizeof(struct mode_type_info));
+ 		typinfo->typid = datumtype;
+ 		get_typlenbyval(datumtype, &typinfo->typLen, &typinfo->typByVal);
+ 	}
+ 
+ 	shouldfree = !(typinfo->typByVal);
+ 
+ 	tuplesort_performsort(sorter);
+ 
+ 	while (tuplesort_getdatum(sorter, true, &val, &isnull))
+ 	{
+ 		if (isnull)
+ 			continue;
+ 
+ 		if (val_freq == 0)
+ 		{
+ 			/* first value - assume modal until shown otherwise */
+ 			mode_val = last_val = val;
+ 			mode_freq = val_freq = 1;
+ 			last_val_is_mode = true;
+ 		}
+ 		else if (DatumGetBool(FunctionCall2(equalfn, val, last_val)))
+ 		{
+ 			/* value equal to previous value */
+ 			if (last_val_is_mode)
+ 				++mode_freq;
+ 			else if (++val_freq > mode_freq)
+ 			{
+ 				if (shouldfree)
+ 				{
+ 					pfree(DatumGetPointer(mode_val));
+ 					pfree(DatumGetPointer(val));
+ 				}
+ 
+ 				mode_val = last_val;
+ 				mode_freq = val_freq;
+ 				last_val_is_mode = true;
+ 			}
+ 			else if (shouldfree)
+ 				pfree(DatumGetPointer(val));
+ 		}
+ 		else
+ 		{
+ 			if (shouldfree && !last_val_is_mode)
+ 				pfree(DatumGetPointer(last_val));
+ 
+ 			last_val_is_mode = false;
+ 			last_val = val;
+ 			val_freq = 1;
+ 		}
+ 	}
+ 
+ 	if (shouldfree && !last_val_is_mode)
+ 		pfree(DatumGetPointer(last_val));
+ 
+ 	if (mode_freq)
+ 		PG_RETURN_DATUM(mode_val);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ 
+ 
+ /*
+  * percentile_disc(float8[])  - discrete percentiles
+  */
+ 
+ Datum percentile_disc_multi_final(PG_FUNCTION_ARGS);
+ 
+ struct pct_info {
+ 	int64	first_row;
+ 	int64	second_row;
+ 	float8	proportion;
+ 	int		idx;
+ };
+ 
+ static int pct_info_cmp(const void *pa, const void *pb)
+ {
+ 	const struct pct_info *a = pa;
+ 	const struct pct_info *b = pb;
+ 	if (a->first_row == b->first_row)
+ 		return (a->second_row < b->second_row) ? -1 : (a->second_row == b->second_row) ? 0 : 1;
+ 	else
+ 		return (a->first_row < b->first_row) ? -1 : 1;
+ }
+ 
+ static struct pct_info *setup_pct_info(int num_percentiles,
+ 									   Datum *percentiles_datum,
+ 									   bool *percentiles_null,
+ 									   int64 rowcount,
+ 									   bool continuous)
+ {
+ 	struct pct_info *pct_info = palloc(num_percentiles * sizeof(struct pct_info));
+ 	int		i;
+ 
+ 	for (i = 0; i < num_percentiles; i++)
+ 	{
+ 		pct_info[i].idx = i;
+ 
+ 		if (percentiles_null[i])
+ 		{
+ 			pct_info[i].first_row = 0;
+ 			pct_info[i].second_row = 0;
+ 			pct_info[i].proportion = 0;
+ 		}
+ 		else
+ 		{
+ 			float8 p = DatumGetFloat8(percentiles_datum[i]);
+ 
+ 			if (p < 0 || p > 1 || isnan(p))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 						 errmsg("percentile value %g must be between 0 and 1", p)));
+ 
+ 			if (continuous)
+ 			{
+ 				pct_info[i].first_row = 1 + floor(p * (rowcount - 1));
+ 				pct_info[i].second_row = 1 + ceil(p * (rowcount - 1));
+ 				pct_info[i].proportion = (p * (rowcount-1)) - floor(p * (rowcount-1));
+ 			}
+ 			else
+ 			{
+ 				/*
+ 				 * We need the smallest K such that (K/N) >= percentile. K starts at 1.
+ 				 * Therefore K >= N*percentile
+ 				 * Therefore K = ceil(N*percentile), minimum 1
+ 				 */
+ 
+ 				pct_info[i].first_row = Max(1, (int64) ceil(rowcount * p));
+ 				pct_info[i].second_row = 0;
+ 				pct_info[i].proportion = 0;
+ 			}
+ 		}
+ 	}
+ 
+ 	qsort(pct_info, num_percentiles, sizeof(struct pct_info), pct_info_cmp);
+ 
+ 	return pct_info;
+ }
+ 
+ Datum
+ percentile_disc_multi_final(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *param;
+ 	Datum	   *percentiles_datum;
+ 	bool	   *percentiles_null;
+ 	int			num_percentiles;
+ 	int64		rowcount = AggSetGetRowCount(fcinfo);
+ 	int64		rownum = 0;
+ 	Tuplesortstate *sorter;
+ 	Oid			datumtype;
+ 	Datum		val;
+ 	bool		isnull;
+ 	Datum	   *result_datum;
+ 	bool	   *result_isnull;
+ 	int			i;
+ 	struct pct_info *pct_info;
+ 
+ 	struct mode_type_info {
+ 		Oid typid;
+ 		int16 typLen;
+ 		bool typByVal;
+ 		char typAlign;
+ 	} *typinfo = fcinfo->flinfo->fn_extra;
+ 
+ 	if (PG_ARGISNULL(0) || rowcount < 1)
+ 		PG_RETURN_NULL();
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ 
+ 	if (!typinfo || typinfo->typid != datumtype)
+ 	{
+ 		if (typinfo)
+ 			pfree(typinfo);
+ 		typinfo = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 									 sizeof(struct mode_type_info));
+ 		typinfo->typid = datumtype;
+ 		get_typlenbyvalalign(datumtype,
+ 							 &typinfo->typLen,
+ 							 &typinfo->typByVal,
+ 							 &typinfo->typAlign);
+ 	}
+ 
+ 	param = PG_GETARG_ARRAYTYPE_P(0);
+ 
+ 	deconstruct_array(param, FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd',
+ 					  &percentiles_datum, &percentiles_null, &num_percentiles);
+ 
+ 	if (num_percentiles == 0)
+ 		PG_RETURN_POINTER(construct_empty_array(datumtype));
+ 
+ 	result_datum = palloc0(num_percentiles * sizeof(Datum));
+ 	result_isnull = palloc0(num_percentiles * sizeof(bool));
+ 
+ 	pct_info = setup_pct_info(num_percentiles,
+ 							  percentiles_datum,
+ 							  percentiles_null,
+ 							  rowcount,
+ 							  false);
+ 
+ 	/*
+ 	 * Start by dealing with any nulls in the param array - those are
+ 	 * sorted to the front on row=0, so set the corresponding result
+ 	 * indexes to null
+ 	 */
+ 	for (i = 0; i < num_percentiles; ++i)
+ 	{
+ 		int idx = pct_info[i].idx;
+ 
+ 		if (pct_info[i].first_row > 0)
+ 			break;
+ 
+ 		result_datum[idx] = (Datum) 0;
+ 		result_isnull[idx] = true;
+ 	}
+ 
+ 	/*
+ 	 * If there's anything left after doing the nulls, then grind the
+ 	 * input and extract the needed values
+ 	 */
+ 	if (i < num_percentiles)
+ 	{
+ 		tuplesort_performsort(sorter);
+ 
+ 		for (; i < num_percentiles; ++i)
+ 		{
+ 			int64 target_row = pct_info[i].first_row;
+ 			int idx = pct_info[i].idx;
+ 
+ 			if (target_row > rownum)
+ 			{
+ 				while (target_row > ++rownum)
+ 				{
+ 					if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ 						elog(ERROR,"missing row in percentile_disc");
+ 				}
+ 
+ 				if (!tuplesort_getdatum(sorter, true, &val, &isnull))
+ 					elog(ERROR,"missing row in percentile_disc");
+ 			}
+ 
+ 			result_datum[idx] = val;
+ 			result_isnull[idx] = isnull;
+ 		}
+ 	}
+ 
+ 	/* We make the output array the same shape as the input */
+ 
+ 	PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull,
+ 										 ARR_NDIM(param),
+ 										 ARR_DIMS(param), ARR_LBOUND(param),
+ 										 datumtype,
+ 										 typinfo->typLen,
+ 										 typinfo->typByVal,
+ 										 typinfo->typAlign));
+ }
+ 
+ static Datum
+ percentile_cont_multi_final_common(FunctionCallInfo fcinfo,
+ 								   Oid expect_type,
+ 								   int16 typLen, bool typByVal, char typAlign,
+ 								   LerpFunc lerpfunc)
+ {
+ 	ArrayType  *param;
+ 	Datum	   *percentiles_datum;
+ 	bool	   *percentiles_null;
+ 	int			num_percentiles;
+ 	int64		rowcount = AggSetGetRowCount(fcinfo);
+ 	int64		rownum = 0;
+ 	int64		rownum_second = 0;
+ 	Tuplesortstate *sorter;
+ 	Oid			datumtype;
+ 	Datum		first_val;
+ 	Datum		second_val;
+ 	bool		isnull;
+ 	Datum	   *result_datum;
+ 	bool	   *result_isnull;
+ 	int			i;
+ 	struct pct_info *pct_info;
+ 
+ 	if (PG_ARGISNULL(0) || rowcount < 1)
+ 		PG_RETURN_NULL();
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ 	Assert(datumtype == expect_type);
+ 
+ 	param = PG_GETARG_ARRAYTYPE_P(0);
+ 
+ 	deconstruct_array(param, FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd',
+ 					  &percentiles_datum, &percentiles_null, &num_percentiles);
+ 
+ 	if (num_percentiles == 0)
+ 		PG_RETURN_POINTER(construct_empty_array(datumtype));
+ 
+ 	result_datum = palloc0(num_percentiles * sizeof(Datum));
+ 	result_isnull = palloc0(num_percentiles * sizeof(bool));
+ 
+ 	pct_info = setup_pct_info(num_percentiles,
+ 							  percentiles_datum,
+ 							  percentiles_null,
+ 							  rowcount,
+ 							  true);
+ 
+ 	/*
+ 	 * Start by dealing with any nulls in the param array - those are
+ 	 * sorted to the front on row=0, so set the corresponding result
+ 	 * indexes to null
+ 	 */
+ 	for (i = 0; i < num_percentiles; ++i)
+ 	{
+ 		int idx = pct_info[i].idx;
+ 
+ 		if (pct_info[i].first_row > 0)
+ 			break;
+ 
+ 		result_datum[idx] = (Datum) 0;
+ 		result_isnull[idx] = true;
+ 	}
+ 
+ 	/*
+ 	 * If there's anything left after doing the nulls, then grind the
+ 	 * input and extract the needed values
+ 	 */
+ 	if (i < num_percentiles)
+ 	{
+ 		tuplesort_performsort(sorter);
+ 
+ 		for (; i < num_percentiles; ++i)
+ 		{
+ 			int64 target_row = pct_info[i].first_row;
+ 			bool need_lerp = pct_info[i].second_row > target_row;
+ 			int idx = pct_info[i].idx;
+ 
+ 			if (target_row > rownum_second)
+ 			{
+ 				rownum = rownum_second;
+ 
+ 				while (target_row > ++rownum)
+ 				{
+ 					if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ 						elog(ERROR,"missing row in percentile_cont");
+ 				}
+ 
+ 				if (!tuplesort_getdatum(sorter, true, &first_val, &isnull) || isnull)
+ 					elog(ERROR,"missing row in percentile_cont");
+ 
+ 				rownum_second = rownum;
+ 
+ 				if (need_lerp)
+ 				{
+ 					if (!tuplesort_getdatum(sorter, true, &second_val, &isnull) || isnull)
+ 						elog(ERROR,"missing row in percentile_cont");
+ 					++rownum_second;
+ 				}
+ 			}
+ 			else if (target_row == rownum_second)
+ 			{
+ 				first_val = second_val;
+ 				rownum = rownum_second;
+ 
+ 				if (need_lerp)
+ 				{
+ 					if (!tuplesort_getdatum(sorter, true, &second_val, &isnull) || isnull)
+ 						elog(ERROR,"missing row in percentile_cont");
+ 					++rownum_second;
+ 				}
+ 			}
+ 
+ 			if (need_lerp)
+ 			{
+ 				result_datum[idx] = lerpfunc(first_val, second_val, pct_info[i].proportion);
+ 			}
+ 			else
+ 				result_datum[idx] = first_val;
+ 
+ 			result_isnull[idx] = false;
+ 		}
+ 	}
+ 
+ 	/* We make the output array the same shape as the input */
+ 
+ 	PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull,
+ 										 ARR_NDIM(param),
+ 										 ARR_DIMS(param), ARR_LBOUND(param),
+ 										 expect_type,
+ 										 typLen,
+ 										 typByVal,
+ 										 typAlign));
+ }
+ 
+ 
+ /*
+  * percentile_cont(float8[]) within group (float8)  - continuous percentiles
+  */
+ 
+ Datum percentile_cont_float8_multi_final(PG_FUNCTION_ARGS);
+ Datum percentile_cont_interval_multi_final(PG_FUNCTION_ARGS);
+ 
+ Datum
+ percentile_cont_float8_multi_final(PG_FUNCTION_ARGS)
+ {
+ 	return percentile_cont_multi_final_common(fcinfo,
+ 											  FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd',
+ 											  float8_lerp);
+ }
+ 
+ /*
+  * percentile_cont(float8[]) within group (Interval)  - continuous percentiles
+  */
+ 
+ Datum
+ percentile_cont_interval_multi_final(PG_FUNCTION_ARGS)
+ {
+ 	return percentile_cont_multi_final_common(fcinfo,
+ 											  INTERVALOID, 16, false, 'd',
+ 											  interval_lerp);
+ }
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 22,27 ****
--- 22,28 ----
  #include "access/sysattr.h"
  #include "catalog/dependency.h"
  #include "catalog/indexing.h"
+ #include "catalog/pg_aggregate.h"
  #include "catalog/pg_authid.h"
  #include "catalog/pg_collation.h"
  #include "catalog/pg_constraint.h"
***************
*** 293,298 **** static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
--- 294,302 ----
  static int print_function_arguments(StringInfo buf, HeapTuple proctup,
  						 bool print_table_args, bool print_defaults);
  static void print_function_rettype(StringInfo buf, HeapTuple proctup);
+ static void print_aggregate_arguments(StringInfo buf,
+ 									  HeapTuple proctup, HeapTuple aggtup,
+ 									  bool print_defaults);
  static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
  				 Bitmapset *rels_used);
  static bool refname_is_unique(char *refname, deparse_namespace *dpns,
***************
*** 402,407 **** static char *generate_function_name(Oid funcid, int nargs,
--- 406,413 ----
  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,2272 **** print_function_arguments(StringInfo buf, HeapTuple proctup,
--- 2273,2421 ----
  
  
  /*
+  * 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,7393 **** static void
--- 7537,7616 ----
  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,7455 **** 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, ')');
  }
  
  /*
--- 7665,7670 ----
*** a/src/backend/utils/sort/tuplesort.c
--- b/src/backend/utils/sort/tuplesort.c
***************
*** 1411,1433 **** tuplesort_performsort(Tuplesortstate *state)
   * Internal routine to fetch the next tuple in either forward or back
   * direction into *stup.  Returns FALSE if no more tuples.
   * If *should_free is set, the caller must pfree stup.tuple when done with it.
   */
  static bool
  tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  						  SortTuple *stup, bool *should_free)
  {
  	unsigned int tuplen;
  
  	switch (state->status)
  	{
  		case TSS_SORTEDINMEM:
  			Assert(forward || state->randomAccess);
! 			*should_free = false;
  			if (forward)
  			{
  				if (state->current < state->memtupcount)
  				{
! 					*stup = state->memtuples[state->current++];
  					return true;
  				}
  				state->eof_reached = true;
--- 1411,1439 ----
   * Internal routine to fetch the next tuple in either forward or back
   * direction into *stup.  Returns FALSE if no more tuples.
   * If *should_free is set, the caller must pfree stup.tuple when done with it.
+  * stup may be null to move without fetching.
   */
  static bool
  tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  						  SortTuple *stup, bool *should_free)
  {
  	unsigned int tuplen;
+ 	SortTuple dummy;
+ 	SortTuple *ptup = stup ? stup : &dummy;
  
  	switch (state->status)
  	{
  		case TSS_SORTEDINMEM:
  			Assert(forward || state->randomAccess);
! 			if (should_free)
! 				*should_free = false;
  			if (forward)
  			{
  				if (state->current < state->memtupcount)
  				{
! 					if (stup)
! 						*stup = state->memtuples[state->current];
! 					state->current++;
  					return true;
  				}
  				state->eof_reached = true;
***************
*** 1459,1479 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  					if (state->current <= 0)
  						return false;
  				}
! 				*stup = state->memtuples[state->current - 1];
  				return true;
  			}
  			break;
  
  		case TSS_SORTEDONTAPE:
  			Assert(forward || state->randomAccess);
! 			*should_free = true;
  			if (forward)
  			{
  				if (state->eof_reached)
  					return false;
  				if ((tuplen = getlen(state, state->result_tape, true)) != 0)
  				{
! 					READTUP(state, stup, state->result_tape, tuplen);
  					return true;
  				}
  				else
--- 1465,1489 ----
  					if (state->current <= 0)
  						return false;
  				}
! 				if (stup)
! 					*stup = state->memtuples[state->current - 1];
  				return true;
  			}
  			break;
  
  		case TSS_SORTEDONTAPE:
  			Assert(forward || state->randomAccess);
! 			if (should_free)
! 				*should_free = true;
  			if (forward)
  			{
  				if (state->eof_reached)
  					return false;
  				if ((tuplen = getlen(state, state->result_tape, true)) != 0)
  				{
! 					READTUP(state, ptup, state->result_tape, tuplen);
! 					if (!stup && dummy.tuple)
! 						pfree(dummy.tuple);
  					return true;
  				}
  				else
***************
*** 1546,1557 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  									  state->result_tape,
  									  tuplen))
  				elog(ERROR, "bogus tuple length in backward scan");
! 			READTUP(state, stup, state->result_tape, tuplen);
  			return true;
  
  		case TSS_FINALMERGE:
  			Assert(forward);
! 			*should_free = true;
  
  			/*
  			 * This code should match the inner loop of mergeonerun().
--- 1556,1570 ----
  									  state->result_tape,
  									  tuplen))
  				elog(ERROR, "bogus tuple length in backward scan");
! 			READTUP(state, ptup, state->result_tape, tuplen);
! 			if (!stup && dummy.tuple)
! 				pfree(dummy.tuple);
  			return true;
  
  		case TSS_FINALMERGE:
  			Assert(forward);
! 			if (should_free)
! 				*should_free = true;
  
  			/*
  			 * This code should match the inner loop of mergeonerun().
***************
*** 1563,1573 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  				int			tupIndex;
  				SortTuple  *newtup;
  
! 				*stup = state->memtuples[0];
  				/* returned tuple is no longer counted in our memory space */
! 				if (stup->tuple)
  				{
! 					tuplen = GetMemoryChunkSpace(stup->tuple);
  					state->availMem += tuplen;
  					state->mergeavailmem[srcTape] += tuplen;
  				}
--- 1576,1586 ----
  				int			tupIndex;
  				SortTuple  *newtup;
  
! 				*ptup = state->memtuples[0];
  				/* returned tuple is no longer counted in our memory space */
! 				if (ptup->tuple)
  				{
! 					tuplen = GetMemoryChunkSpace(ptup->tuple);
  					state->availMem += tuplen;
  					state->mergeavailmem[srcTape] += tuplen;
  				}
***************
*** 1598,1603 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
--- 1611,1618 ----
  				newtup->tupindex = state->mergefreelist;
  				state->mergefreelist = tupIndex;
  				state->mergeavailslots[srcTape]++;
+ 				if (!stup && dummy.tuple)
+ 					pfree(dummy.tuple);
  				return true;
  			}
  			return false;
***************
*** 1620,1639 **** tuplesort_gettupleslot(Tuplesortstate *state, bool forward,
  	MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext);
  	SortTuple	stup;
  	bool		should_free;
  
! 	if (!tuplesort_gettuple_common(state, forward, &stup, &should_free))
! 		stup.tuple = NULL;
  
  	MemoryContextSwitchTo(oldcontext);
  
! 	if (stup.tuple)
  	{
! 		ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free);
  		return true;
  	}
  	else
  	{
! 		ExecClearTuple(slot);
  		return false;
  	}
  }
--- 1635,1656 ----
  	MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext);
  	SortTuple	stup;
  	bool		should_free;
+ 	bool        found;
  
! 	found = tuplesort_gettuple_common(state, forward, (slot ? &stup : NULL), &should_free);
  
  	MemoryContextSwitchTo(oldcontext);
  
! 	if (found)
  	{
! 		if (slot)
! 			ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free);
  		return true;
  	}
  	else
  	{
! 		if (slot)
! 			ExecClearTuple(slot);
  		return false;
  	}
  }
***************
*** 1692,1715 **** tuplesort_getdatum(Tuplesortstate *state, bool forward,
  	SortTuple	stup;
  	bool		should_free;
  
! 	if (!tuplesort_gettuple_common(state, forward, &stup, &should_free))
  	{
  		MemoryContextSwitchTo(oldcontext);
  		return false;
  	}
  
! 	if (stup.isnull1 || state->datumTypeByVal)
  	{
! 		*val = stup.datum1;
! 		*isNull = stup.isnull1;
! 	}
! 	else
! 	{
! 		if (should_free)
  			*val = stup.datum1;
  		else
! 			*val = datumCopy(stup.datum1, false, state->datumTypeLen);
! 		*isNull = false;
  	}
  
  	MemoryContextSwitchTo(oldcontext);
--- 1709,1735 ----
  	SortTuple	stup;
  	bool		should_free;
  
! 	if (!tuplesort_gettuple_common(state, forward, (val ? &stup : NULL), &should_free))
  	{
  		MemoryContextSwitchTo(oldcontext);
  		return false;
  	}
  
! 	if (val)
  	{
! 		if (stup.isnull1 || state->datumTypeByVal)
! 		{
  			*val = stup.datum1;
+ 			*isNull = stup.isnull1;
+ 		}
  		else
! 		{
! 			if (should_free)
! 				*val = stup.datum1;
! 			else
! 				*val = datumCopy(stup.datum1, false, state->datumTypeLen);
! 			*isNull = false;
! 		}
  	}
  
  	MemoryContextSwitchTo(oldcontext);
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 229,234 **** static void getTableData(TableInfo *tblinfo, int numTables, bool oids);
--- 229,235 ----
  static void makeTableDataInfo(TableInfo *tbinfo, bool oids);
  static void buildMatViewRefreshDependencies(Archive *fout);
  static void getTableDataFKConstraints(void);
+ static char *format_aggregate_arguments(FuncInfo *finfo, char *funcargs);
  static char *format_function_arguments(FuncInfo *finfo, char *funcargs,
  									   bool is_agg);
  static char *format_function_arguments_old(Archive *fout,
***************
*** 9363,9368 **** dumpProcLang(Archive *fout, ProcLangInfo *plang)
--- 9364,9385 ----
  }
  
  /*
+  * 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
***************
*** 11418,11432 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11435,11456 ----
  	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)
***************
*** 11442,11452 **** dumpAgg(Archive *fout, AggInfo *agginfo)
  	selectSourceSchema(fout, agginfo->aggfn.dobj.namespace->dobj.name);
  
  	/* Get aggregate-specific details */
! 	if (fout->remoteVersion >= 80400)
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "agginitval, "
  						  "'t'::boolean AS convertok, "
  						  "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs, "
--- 11466,11496 ----
  	selectSourceSchema(fout, agginfo->aggfn.dobj.namespace->dobj.name);
  
  	/* Get aggregate-specific details */
! 	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, "
***************
*** 11461,11466 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11505,11513 ----
  		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 "
***************
*** 11473,11478 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11520,11528 ----
  		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 "
***************
*** 11485,11490 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11535,11543 ----
  		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 "
***************
*** 11497,11502 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11550,11558 ----
  						  "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 "
***************
*** 11509,11514 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11565,11573 ----
  	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");
***************
*** 11516,11526 **** dumpAgg(Archive *fout, AggInfo *agginfo)
  	aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
  	aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
  	aggsortop = PQgetvalue(res, 0, i_aggsortop);
  	aggtranstype = PQgetvalue(res, 0, i_aggtranstype);
  	agginitval = PQgetvalue(res, 0, i_agginitval);
  	convertok = (PQgetvalue(res, 0, i_convertok)[0] == 't');
  
! 	if (fout->remoteVersion >= 80400)
  	{
  		/* 8.4 or later; we rely on server-side code for most of the work */
  		char	   *funcargs;
--- 11575,11599 ----
  	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 >= 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;
***************
*** 11550,11585 **** dumpAgg(Archive *fout, AggInfo *agginfo)
  	if (fout->remoteVersion >= 70300)
  	{
  		/* If using 7.3's regproc or regtype, data is already quoted */
! 		appendPQExpBuffer(details, "    SFUNC = %s,\n    STYPE = %s",
! 						  aggtransfn,
! 						  aggtranstype);
  	}
  	else if (fout->remoteVersion >= 70100)
  	{
  		/* format_type quotes, regproc does not */
! 		appendPQExpBuffer(details, "    SFUNC = %s,\n    STYPE = %s",
  						  fmtId(aggtransfn),
  						  aggtranstype);
  	}
  	else
  	{
  		/* need quotes all around */
! 		appendPQExpBuffer(details, "    SFUNC = %s,\n",
  						  fmtId(aggtransfn));
  		appendPQExpBuffer(details, "    STYPE = %s",
  						  fmtId(aggtranstype));
  	}
  
! 	if (!PQgetisnull(res, 0, i_agginitval))
  	{
! 		appendPQExpBuffer(details, ",\n    INITCOND = ");
! 		appendStringLiteralAH(details, agginitval, fout);
  	}
  
! 	if (strcmp(aggfinalfn, "-") != 0)
  	{
! 		appendPQExpBuffer(details, ",\n    FINALFUNC = %s",
! 						  aggfinalfn);
  	}
  
  	aggsortop = convertOperatorReference(fout, aggsortop);
--- 11623,11680 ----
  	if (fout->remoteVersion >= 70300)
  	{
  		/* If using 7.3's regproc or regtype, data is already quoted */
! 		/*
! 		 * either or both of SFUNC and STYPE might be missing in >90400,
! 		 * but if SFUNC is missing, then FINALFUNC will always be present,
! 		 * and if SFUNC is present then STYPE must also be present; the
! 		 * code below relies on these conditions to keep the commas in the
! 		 * right places. STRICT must be forced to false if SFUNC is present.
! 		 */
! 
! 		if (strcmp(aggtransfn,"-") != 0)
! 		{
! 			appendPQExpBuffer(details, "\n    SFUNC = %s,", aggtransfn);
! 			isstrict = false;
! 		}
! 
! 		if (strcmp(aggtranstype,"-") != 0)
! 			appendPQExpBuffer(details, "\n    STYPE = %s", aggtranstype);
! 		else
! 			has_comma = true;
  	}
  	else if (fout->remoteVersion >= 70100)
  	{
  		/* format_type quotes, regproc does not */
! 		appendPQExpBuffer(details, "\n    SFUNC = %s,\n    STYPE = %s",
  						  fmtId(aggtransfn),
  						  aggtranstype);
  	}
  	else
  	{
  		/* need quotes all around */
! 		appendPQExpBuffer(details, "\n    SFUNC = %s,\n",
  						  fmtId(aggtransfn));
  		appendPQExpBuffer(details, "    STYPE = %s",
  						  fmtId(aggtranstype));
  	}
  
! 	if (strcmp(aggfinalfn, "-") != 0)
  	{
! 		appendPQExpBuffer(details, "%s\n    FINALFUNC = %s",
! 						  (has_comma ? "" : ","),
! 						  aggfinalfn);
  	}
  
! 	if (hypothetical)
! 		appendPQExpBuffer(details, ",\n    HYPOTHETICAL");
! 
! 	if (isstrict)
! 		appendPQExpBuffer(details, ",\n    STRICT");
! 
! 	if (!PQgetisnull(res, 0, i_agginitval))
  	{
! 		appendPQExpBuffer(details, ",\n    INITCOND = ");
! 		appendStringLiteralAH(details, agginitval, fout);
  	}
  
  	aggsortop = convertOperatorReference(fout, aggsortop);
***************
*** 11589,11594 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11684,11696 ----
  						  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
  	 */
***************
*** 11596,11602 **** dumpAgg(Archive *fout, AggInfo *agginfo)
  					  fmtId(agginfo->aggfn.dobj.namespace->dobj.name),
  					  aggsig);
  
! 	appendPQExpBuffer(q, "CREATE AGGREGATE %s (\n%s\n);\n",
  					  aggfullsig, details->data);
  
  	appendPQExpBuffer(labelq, "AGGREGATE %s", aggsig);
--- 11698,11704 ----
  					  fmtId(agginfo->aggfn.dobj.namespace->dobj.name),
  					  aggsig);
  
! 	appendPQExpBuffer(q, "CREATE AGGREGATE %s (%s\n);\n",
  					  aggfullsig, details->data);
  
  	appendPQExpBuffer(labelq, "AGGREGATE %s", aggsig);
***************
*** 11625,11631 **** dumpAgg(Archive *fout, AggInfo *agginfo)
  	/*
  	 * Since there is no GRANT ON AGGREGATE syntax, we have to make the ACL
  	 * command look like a function's GRANT; in particular this affects the
! 	 * syntax for zero-argument aggregates.
  	 */
  	free(aggsig);
  	free(aggsig_tag);
--- 11727,11733 ----
  	/*
  	 * Since there is no GRANT ON AGGREGATE syntax, we have to make the ACL
  	 * command look like a function's GRANT; in particular this affects the
! 	 * syntax for zero-argument aggregates and ordered set functions.
  	 */
  	free(aggsig);
  	free(aggsig_tag);
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 72,78 **** describeAggregates(const char *pattern, bool verbose, bool showSystem)
  					  gettext_noop("Name"),
  					  gettext_noop("Result data type"));
  
! 	if (pset.sversion >= 80400)
  		appendPQExpBuffer(&buf,
  						  "  CASE WHEN p.pronargs = 0\n"
  						  "    THEN CAST('*' AS pg_catalog.text)\n"
--- 72,82 ----
  					  gettext_noop("Name"),
  					  gettext_noop("Result data type"));
  
! 	if (pset.sversion >= 90400)
! 		appendPQExpBuffer(&buf,
! 					      " pg_catalog.pg_get_aggregate_arguments(p.oid) AS \"%s\",\n",
! 						  gettext_noop("Argument data types"));
! 	else if (pset.sversion >= 80400)
  		appendPQExpBuffer(&buf,
  						  "  CASE WHEN p.pronargs = 0\n"
  						  "    THEN CAST('*' AS pg_catalog.text)\n"
***************
*** 254,260 **** describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
  					  gettext_noop("Schema"),
  					  gettext_noop("Name"));
  
! 	if (pset.sversion >= 80400)
  		appendPQExpBuffer(&buf,
  					"  pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n"
  				 "  pg_catalog.pg_get_function_arguments(p.oid) as \"%s\",\n"
--- 258,283 ----
  					  gettext_noop("Schema"),
  					  gettext_noop("Name"));
  
! 	if (pset.sversion >= 90400)
! 		appendPQExpBuffer(&buf,
! 					"  pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n"
! 				 "  CASE WHEN p.proisagg THEN pg_catalog.pg_get_aggregate_arguments(p.oid)\n"
!                  "       ELSE pg_catalog.pg_get_function_arguments(p.oid) END as \"%s\",\n"
! 						  " CASE\n"
! 						  "  WHEN p.proisagg THEN '%s'\n"
! 						  "  WHEN p.proiswindow THEN '%s'\n"
! 						  "  WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN '%s'\n"
! 						  "  ELSE '%s'\n"
! 						  " END as \"%s\"",
! 						  gettext_noop("Result data type"),
! 						  gettext_noop("Argument data types"),
! 		/* translator: "agg" is short for "aggregate" */
! 						  gettext_noop("agg"),
! 						  gettext_noop("window"),
! 						  gettext_noop("trigger"),
! 						  gettext_noop("normal"),
! 						  gettext_noop("Type"));
! 	else if (pset.sversion >= 80400)
  		appendPQExpBuffer(&buf,
  					"  pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n"
  				 "  pg_catalog.pg_get_function_arguments(p.oid) as \"%s\",\n"
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
***************
*** 32,37 ****
--- 32,40 ----
   *	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,49 **** CATALOG(pg_aggregate,2600) BKI_WITHOUT_OIDS
--- 47,55 ----
  	regproc		aggfinalfn;
  	Oid			aggsortop;
  	Oid			aggtranstype;
+ 	Oid			aggtranssortop;
+ 	int32		aggordnargs;
+ 	bool		aggisordsetfunc;
  
  #ifdef CATALOG_VARLEN			/* variable-length fields start here */
  	text		agginitval;
***************
*** 62,74 **** typedef FormData_pg_aggregate *Form_pg_aggregate;
   * ----------------
   */
  
! #define Natts_pg_aggregate				6
  #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
  
  
  /* ----------------
--- 68,83 ----
   * ----------------
   */
  
! #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_aggtranssortop	6
! #define Anum_pg_aggregate_aggordnargs	7
! #define Anum_pg_aggregate_aggisordsetfunc	8
! #define Anum_pg_aggregate_agginitval	9
  
  
  /* ----------------
***************
*** 77,239 **** 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}" ));
  
  /* 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_ ));
  
  /* 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_ ));
  
  /* 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_ ));
  
  /* count */
! DATA(insert ( 2147	int8inc_any		-				0		20		"0" ));
! DATA(insert ( 2803	int8inc			-				0		20		"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}" ));
  
  /* 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}" ));
  
  /* 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}" ));
  
  /* 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}" ));
  
  /* 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}" ));
  
  /* 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}" ));
  
  /* 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}" ));
  
  /* 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_ ));
  
  /* 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_ ));
  
  /* xml */
! DATA(insert ( 2901 xmlconcat2	  -					0	142		_null_ ));
  
  /* array */
! DATA(insert ( 2335	array_agg_transfn	array_agg_finalfn		0	2281	_null_ ));
  
  /* text */
! DATA(insert ( 3538	string_agg_transfn	string_agg_finalfn		0	2281	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	bytea_string_agg_transfn	bytea_string_agg_finalfn		0	2281	_null_ ));
  
  /* json */
! DATA(insert ( 3175	json_agg_transfn	json_agg_finalfn		0	2281	_null_ ));
  
  /*
   * prototypes for functions in pg_aggregate.c
--- 86,261 ----
   */
  
  /* avg */
! 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	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		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		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 -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 -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 -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 -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 -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 -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 -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 -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		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		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		0 -1 f _null_ ));
  
  /* array */
! 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	0 -1 f _null_ ));
  
  /* bytea */
! 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	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 ( 3968 	- 			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,246 **** DATA(insert ( 3175	json_agg_transfn	json_agg_finalfn		0	2281	_null_ ));
--- 263,269 ----
  extern Oid AggregateCreate(const char *aggName,
  				Oid aggNamespace,
  				int numArgs,
+ 				int numDirectArgs,
  				oidvector *parameterTypes,
  				Datum allParameterTypes,
  				Datum parameterModes,
***************
*** 249,255 **** extern Oid AggregateCreate(const char *aggName,
  				List *aggtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
! 				const char *agginitval);
  
  #endif   /* PG_AGGREGATE_H */
--- 272,282 ----
  				List *aggtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
+ 				List *aggtranssortopName,
  				Oid aggTransType,
! 				const char *agginitval,
! 				bool isStrict,
! 				bool isOrderedSetFunc,
! 				bool isHypotheticalSet);
  
  #endif   /* PG_AGGREGATE_H */
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 1964,1969 **** DATA(insert OID = 2232 (  pg_get_function_identity_arguments	   PGNSP PGUID 12 1
--- 1964,1973 ----
  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");
***************
*** 4729,4734 **** DESCR("SP-GiST support for quad tree over range");
--- 4733,4806 ----
  /* 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 = 3968 ( rank		PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_	aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("hypothetical rank");
+ 
+ DATA(insert OID = 3969 ( rank_final		PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_	hypothetical_rank_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3970 ( dense_rank		PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_	aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("rank of hypothetical row without gaps");
+ 
+ DATA(insert OID = 3971 ( dense_rank_final		PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_	hypothetical_dense_rank_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3972 ( percent_rank		PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_	aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("fractional ranking of hypothetical row within a group");
+ 
+ DATA(insert OID = 3973 ( percent_rank_final		PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_	hypothetical_percent_rank_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3974 ( cume_dist		PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_	aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("cumulative distribution of hypothetical row in a group");
+ 
+ DATA(insert OID = 3975 ( cume_dist_final		PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_	hypothetical_cume_dist_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3976 ( mode		PGNSP PGUID 12 1 0 0 0 t f f f t f i 1 0 2283 2283 _null_ _null_ _null_ _null_	aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("most common value in group");
+ DATA(insert OID = 3977 ( mode_final		PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2283 2283 _null_ _null_ _null_ _null_	mode_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3978 ( percentile_disc	PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 2277 "1022 2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("multiple discrete percentiles");
+ 
+ DATA(insert OID = 3979 ( percentile_disc_multi_final	PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2277 "1022 2283" _null_ _null_ _null_ _null_ percentile_disc_multi_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3980 ( percentile_cont	PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 1022 "1022 701" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("multiple continuous percentiles of float8 values");
+ 
+ DATA(insert OID = 3981 ( percentile_cont_float8_multi_final	PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1022 "1022 701" _null_ _null_ _null_ _null_ percentile_cont_float8_multi_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3982 ( percentile_cont	PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 1187 "1022 1186" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("multiple continuous percentiles of interval values");
+ 
+ DATA(insert OID = 3983 ( percentile_cont_interval_multi_final	PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1187 "1022 1186" _null_ _null_ _null_ _null_ percentile_cont_interval_multi_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
  /*
   * Symbolic values for provolatile column: these indicate whether the result
   * of a function is dependent *only* on the values of its explicit arguments,
*** a/src/include/fmgr.h
--- b/src/include/fmgr.h
***************
*** 651,656 **** extern void **find_rendezvous_variable(const char *varName);
--- 651,685 ----
  extern int AggCheckCallContext(FunctionCallInfo fcinfo,
  					MemoryContext *aggcontext);
  
+ typedef struct Tuplesortstate fmTuplesortstate;
+ typedef struct tupleDesc *fmTupleDesc;
+ typedef struct TupleTableSlot fmTupleTableSlot;
+ 
+ extern int64 AggSetGetRowCount(FunctionCallInfo fcinfo);
+ 
+ extern void AggSetGetSortInfo(FunctionCallInfo fcinfo,
+ 							  fmTuplesortstate **sortstate,
+ 							  fmTupleDesc *tupdesc,
+ 							  fmTupleTableSlot **tupslot,
+ 							  Oid *datumtype);
+ 
+ /* int16 rather than AttrNumber here to avoid includes */
+ extern int AggSetGetDistinctInfo(FunctionCallInfo fcinfo,
+ 								 fmTupleTableSlot **tupslot,
+ 								 int16 **sortColIdx,
+ 								 FmgrInfo **equalfns);
+ 
+ /* int16 rather than AttrNumber here to avoid includes */
+ extern int AggSetGetSortOperators(FunctionCallInfo fcinfo,
+ 								  int16 **sortColIdx,
+ 								  Oid **sortOperators,
+ 								  Oid **sortEqOperators,
+ 								  Oid **sortCollations,
+ 								  bool **sortNullsFirst);
+ 
+ extern void AggSetGetPerTupleContext(FunctionCallInfo fcinfo,
+ 						 MemoryContext *memcontext);
+ 
  /*
   * We allow plugin modules to hook function entry/exit.  This is intended
   * as support for loadable security policy modules, which may want to
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 588,593 **** typedef struct AggrefExprState
--- 588,594 ----
  {
  	ExprState	xprstate;
  	List	   *args;			/* states of argument expressions */
+ 	List	   *orddirectargs;	/* Ordered direct arguments */
  	ExprState  *aggfilter;		/* FILTER expression */
  	int			aggno;			/* ID number for agg within its plan node */
  } AggrefExprState;
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 425,431 **** typedef enum NodeTag
  	T_WindowObjectData,			/* private in nodeWindowAgg.c */
  	T_TIDBitmap,				/* in nodes/tidbitmap.h */
  	T_InlineCodeBlock,			/* in nodes/parsenodes.h */
! 	T_FdwRoutine				/* in foreign/fdwapi.h */
  } NodeTag;
  
  /*
--- 425,432 ----
  	T_WindowObjectData,			/* private in nodeWindowAgg.c */
  	T_TIDBitmap,				/* in nodes/tidbitmap.h */
  	T_InlineCodeBlock,			/* in nodes/parsenodes.h */
! 	T_FdwRoutine,				/* in foreign/fdwapi.h */
! 	T_AggStatePerAggData			/* private in nodeAgg.c */
  } NodeTag;
  
  /*
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 303,308 **** typedef struct FuncCall
--- 303,309 ----
  	bool		agg_star;		/* argument was really '*' */
  	bool		agg_distinct;	/* arguments were labeled DISTINCT */
  	bool		func_variadic;	/* last argument was labeled VARIADIC */
+ 	bool		has_within_group;	/* WITHIN GROUP clause,if any */
  	struct WindowDef *over;		/* OVER clause, if any */
  	int			location;		/* token location, or -1 if unknown */
  } FuncCall;
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 247,255 **** typedef struct Aggref
--- 247,258 ----
  	List	   *args;			/* arguments and sort expressions */
  	List	   *aggorder;		/* ORDER BY (list of SortGroupClause) */
  	List	   *aggdistinct;	/* DISTINCT (list of SortGroupClause) */
+ 	List	   *orddirectargs;	/* Direct arguments for ordered set functions */
  	Expr	   *aggfilter;		/* FILTER expression */
  	bool		aggstar;		/* TRUE if argument list was really '*' */
  	bool		aggvariadic;	/* TRUE if VARIADIC was used in call */
+ 	bool		isordset;	/* If node is from an ordered set function */
+ 	bool		ishypothetical;	/* If node is from a hypothetical set function */
  	Index		agglevelsup;	/* > 0 if agg belongs to outer query */
  	int			location;		/* token location, or -1 if unknown */
  } Aggref;
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 412,417 **** PG_KEYWORD("where", WHERE, RESERVED_KEYWORD)
--- 412,418 ----
  PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("window", WINDOW, RESERVED_KEYWORD)
  PG_KEYWORD("with", WITH, RESERVED_KEYWORD)
+ PG_KEYWORD("within", WITHIN, UNRESERVED_KEYWORD)
  PG_KEYWORD("without", WITHOUT, UNRESERVED_KEYWORD)
  PG_KEYWORD("work", WORK, UNRESERVED_KEYWORD)
  PG_KEYWORD("wrapper", WRAPPER, UNRESERVED_KEYWORD)
*** a/src/include/parser/parse_agg.h
--- b/src/include/parser/parse_agg.h
***************
*** 17,23 ****
  
  extern void transformAggregateCall(ParseState *pstate, Aggref *agg,
  					   List *args, List *aggorder,
! 					   bool agg_distinct);
  extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
  						WindowDef *windef);
  
--- 17,23 ----
  
  extern void transformAggregateCall(ParseState *pstate, Aggref *agg,
  					   List *args, List *aggorder,
! 					   bool agg_distinct, bool agg_within_group);
  extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
  						WindowDef *windef);
  
***************
*** 34,37 **** extern void build_aggregate_fnexprs(Oid *agg_input_types,
--- 34,51 ----
  						Expr **transfnexpr,
  						Expr **finalfnexpr);
  
+ void
+ build_orderedset_fnexprs(Oid *agg_input_types,
+ 					int agg_num_inputs,
+ 					bool agg_variadic,
+ 					Oid agg_result_type,
+ 					Oid agg_input_collation,
+ 					Oid *agg_input_collation_array,
+ 					Oid finalfn_oid,
+ 					Expr **finalfnexpr);
+ 
+ int get_aggregate_argtypes(Aggref *aggref, 
+ 				Oid *inputTypes, 
+ 				Oid *inputCollations);
+ 
  #endif   /* PARSE_AGG_H */
*** a/src/include/parser/parse_clause.h
--- b/src/include/parser/parse_clause.h
***************
*** 31,37 **** extern List *transformGroupClause(ParseState *pstate, List *grouplist,
  					 ParseExprKind exprKind, bool useSQL99);
  extern List *transformSortClause(ParseState *pstate, List *orderlist,
  					List **targetlist, ParseExprKind exprKind,
! 					bool resolveUnknown, bool useSQL99);
  
  extern List *transformWindowDefinitions(ParseState *pstate,
  						   List *windowdefs,
--- 31,37 ----
  					 ParseExprKind exprKind, bool useSQL99);
  extern List *transformSortClause(ParseState *pstate, List *orderlist,
  					List **targetlist, ParseExprKind exprKind,
! 					bool resolveUnknown, bool useSQL99, bool keepDuplicates);
  
  extern List *transformWindowDefinitions(ParseState *pstate,
  						   List *windowdefs,
*** a/src/include/parser/parse_func.h
--- b/src/include/parser/parse_func.h
***************
*** 38,51 **** typedef enum
  	FUNCDETAIL_NORMAL,			/* found a matching regular function */
  	FUNCDETAIL_AGGREGATE,		/* found a matching aggregate function */
  	FUNCDETAIL_WINDOWFUNC,		/* found a matching window function */
! 	FUNCDETAIL_COERCION			/* it's a type coercion request */
  } FuncDetailCode;
  
- 
  extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
! 				  List *agg_order, Expr *agg_filter,
! 				  bool agg_star, bool agg_distinct, bool func_variadic,
! 				  WindowDef *over, bool is_column, int location);
  
  extern FuncDetailCode func_get_detail(List *funcname,
  				List *fargs, List *fargnames,
--- 38,48 ----
  	FUNCDETAIL_NORMAL,			/* found a matching regular function */
  	FUNCDETAIL_AGGREGATE,		/* found a matching aggregate function */
  	FUNCDETAIL_WINDOWFUNC,		/* found a matching window function */
! 	FUNCDETAIL_COERCION,			/* it's a type coercion request */
  } FuncDetailCode;
  
  extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
! 							   int location, FuncCall *fn);
  
  extern FuncDetailCode func_get_detail(List *funcname,
  				List *fargs, List *fargnames,
***************
*** 66,73 **** extern FuncCandidateList func_select_candidate(int nargs,
  
  extern void make_fn_arguments(ParseState *pstate,
  				  List *fargs,
  				  Oid *actual_arg_types,
! 				  Oid *declared_arg_types);
  
  extern const char *funcname_signature_string(const char *funcname, int nargs,
  						  List *argnames, const Oid *argtypes);
--- 63,72 ----
  
  extern void make_fn_arguments(ParseState *pstate,
  				  List *fargs,
+ 				  List *agg_order,
  				  Oid *actual_arg_types,
! 				  Oid *declared_arg_types,
! 				  bool requiresUnification);
  
  extern const char *funcname_signature_string(const char *funcname, int nargs,
  						  List *argnames, const Oid *argtypes);
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 658,663 **** extern Datum pg_get_functiondef(PG_FUNCTION_ARGS);
--- 658,665 ----
  extern Datum pg_get_function_arguments(PG_FUNCTION_ARGS);
  extern Datum pg_get_function_identity_arguments(PG_FUNCTION_ARGS);
  extern Datum pg_get_function_result(PG_FUNCTION_ARGS);
+ extern Datum pg_get_aggregate_arguments(PG_FUNCTION_ARGS);
+ extern Datum pg_get_aggregate_identity_arguments(PG_FUNCTION_ARGS);
  extern char *deparse_expression(Node *expr, List *dpcontext,
  				   bool forceprefix, bool showimplicit);
  extern List *deparse_context_for(const char *aliasname, Oid relid);
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
***************
*** 1249,1254 **** select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
--- 1249,1461 ----
   {"(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:  Incorrect number of arguments for hypothetical set function
+ 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     
*** a/src/test/regress/expected/opr_sanity.out
--- b/src/test/regress/expected/opr_sanity.out
***************
*** 700,708 **** SELECT * FROM funcdescs
  
  -- **************** pg_aggregate ****************
  -- Look for illegal values in pg_aggregate fields.
  SELECT ctid, aggfnoid::oid
  FROM pg_aggregate as p1
! WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0;
   ctid | aggfnoid 
  ------+----------
  (0 rows)
--- 700,716 ----
  
  -- **************** pg_aggregate ****************
  -- Look for illegal values in pg_aggregate fields.
+ -- ordered set functions can't have transfns, and must
+ -- have finalfns, but may or may not have transtypes.
+ -- other aggs must have transfns and transtypes with
+ -- optional finalfns.
  SELECT ctid, aggfnoid::oid
  FROM pg_aggregate as p1
! WHERE aggfnoid = 0
!    OR CASE WHEN aggisordsetfunc
!            THEN aggtransfn <> 0 OR aggfinalfn = 0
!            ELSE aggtransfn = 0 OR aggtranstype = 0
!       END;
   ctid | aggfnoid 
  ------+----------
  (0 rows)
***************
*** 764,771 **** WHERE a.aggfnoid = p.oid AND
      a.aggfinalfn = pfn.oid AND
      (pfn.proretset
       OR NOT binary_coercible(pfn.prorettype, p.prorettype)
!      OR pfn.pronargs != 1
!      OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]));
   aggfnoid | proname | oid | proname 
  ----------+---------+-----+---------
  (0 rows)
--- 772,780 ----
      a.aggfinalfn = pfn.oid AND
      (pfn.proretset
       OR NOT binary_coercible(pfn.prorettype, p.prorettype)
!      OR (aggisordsetfunc IS FALSE
!          AND (pfn.pronargs != 1
!               OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]))));
   aggfnoid | proname | oid | proname 
  ----------+---------+-----+---------
  (0 rows)
***************
*** 857,866 **** ORDER BY 1;
   count("any") | count()
  (1 row)
  
! -- For the same reason, we avoid creating built-in variadic aggregates.
! SELECT oid, proname
! FROM pg_proc AS p
! WHERE proisagg AND provariadic != 0;
   oid | proname 
  -----+---------
  (0 rows)
--- 866,877 ----
   count("any") | count()
  (1 row)
  
! -- For the same reason, we avoid creating built-in variadic aggregates, except
! -- ordered set functions (which have their own syntax and are not subject to
! -- the misplaced ORDER BY issue).
! SELECT p.oid, proname
! FROM pg_proc AS p JOIN pg_aggregate AS a ON (a.aggfnoid=p.oid)
! WHERE proisagg AND provariadic != 0 AND NOT a.aggisordsetfunc;
   oid | proname 
  -----+---------
  (0 rows)
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
***************
*** 481,486 **** select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
--- 481,549 ----
      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;
*** a/src/test/regress/sql/opr_sanity.sql
--- b/src/test/regress/sql/opr_sanity.sql
***************
*** 564,573 **** SELECT * FROM funcdescs
  -- **************** pg_aggregate ****************
  
  -- Look for illegal values in pg_aggregate fields.
  
  SELECT ctid, aggfnoid::oid
  FROM pg_aggregate as p1
! WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0;
  
  -- Make sure the matching pg_proc entry is sensible, too.
  
--- 564,581 ----
  -- **************** pg_aggregate ****************
  
  -- Look for illegal values in pg_aggregate fields.
+ -- ordered set functions can't have transfns, and must
+ -- have finalfns, but may or may not have transtypes.
+ -- other aggs must have transfns and transtypes with
+ -- optional finalfns.
  
  SELECT ctid, aggfnoid::oid
  FROM pg_aggregate as p1
! WHERE aggfnoid = 0
!    OR CASE WHEN aggisordsetfunc
!            THEN aggtransfn <> 0 OR aggfinalfn = 0
!            ELSE aggtransfn = 0 OR aggtranstype = 0
!       END;
  
  -- Make sure the matching pg_proc entry is sensible, too.
  
***************
*** 618,625 **** WHERE a.aggfnoid = p.oid AND
      a.aggfinalfn = pfn.oid AND
      (pfn.proretset
       OR NOT binary_coercible(pfn.prorettype, p.prorettype)
!      OR pfn.pronargs != 1
!      OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]));
  
  -- If transfn is strict then either initval should be non-NULL, or
  -- input type should match transtype so that the first non-null input
--- 626,634 ----
      a.aggfinalfn = pfn.oid AND
      (pfn.proretset
       OR NOT binary_coercible(pfn.prorettype, p.prorettype)
!      OR (aggisordsetfunc IS FALSE
!          AND (pfn.pronargs != 1
!               OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]))));
  
  -- If transfn is strict then either initval should be non-NULL, or
  -- input type should match transtype so that the first non-null input
***************
*** 685,695 **** WHERE p1.oid < p2.oid AND p1.proname = p2.proname AND
      array_dims(p1.proargtypes) != array_dims(p2.proargtypes)
  ORDER BY 1;
  
! -- For the same reason, we avoid creating built-in variadic aggregates.
  
! SELECT oid, proname
! FROM pg_proc AS p
! WHERE proisagg AND provariadic != 0;
  
  -- For the same reason, built-in aggregates with default arguments are no good.
  
--- 694,706 ----
      array_dims(p1.proargtypes) != array_dims(p2.proargtypes)
  ORDER BY 1;
  
! -- For the same reason, we avoid creating built-in variadic aggregates, except
! -- ordered set functions (which have their own syntax and are not subject to
! -- the misplaced ORDER BY issue).
  
! SELECT p.oid, proname
! FROM pg_proc AS p JOIN pg_aggregate AS a ON (a.aggfnoid=p.oid)
! WHERE proisagg AND provariadic != 0 AND NOT a.aggisordsetfunc;
  
  -- For the same reason, built-in aggregates with default arguments are no good.
  
#21Peter Eisentraut
peter_e@gmx.net
In reply to: Atri Sharma (#20)
Re: Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

On Fri, 2013-09-13 at 14:56 +0530, Atri Sharma wrote:

This is our complete patch for implementation of WITHIN GROUP.

Please fix compiler warnings:

inversedistribution.c: In function ‘mode_final’:
inversedistribution.c:276:11: warning: ‘mode_val’ may be used uninitialized in this function [-Wmaybe-uninitialized]
inversedistribution.c:299:8: warning: ‘last_val’ may be used uninitialized in this function [-Wmaybe-uninitialized]

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#22Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Peter Eisentraut (#21)
Re: Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

"Peter" == Peter Eisentraut <peter_e@gmx.net> writes:

Peter> Please fix compiler warnings:

Someone should do the same in WaitForBackgroundWorkerStartup so that
building with -Werror works.

New patch coming shortly.

--
Andrew.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#23Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Peter Eisentraut (#21)
1 attachment(s)
Re: Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

"Peter" == Peter Eisentraut <peter_e@gmx.net> writes:

Peter> Please fix compiler warnings:

Done.

--
Andrew (irc:RhodiumToad)

Attachments:

ordset-20130914.patchtext/x-patchDownload
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 352,358 ****
        <entry><structfield>aggtransfn</structfield></entry>
        <entry><type>regproc</type></entry>
        <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
!       <entry>Transition function</entry>
       </row>
       <row>
        <entry><structfield>aggfinalfn</structfield></entry>
--- 352,358 ----
        <entry><structfield>aggtransfn</structfield></entry>
        <entry><type>regproc</type></entry>
        <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
!       <entry>Transition function (zero if none)</entry>
       </row>
       <row>
        <entry><structfield>aggfinalfn</structfield></entry>
***************
*** 370,376 ****
        <entry><structfield>aggtranstype</structfield></entry>
        <entry><type>oid</type></entry>
        <entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
!       <entry>Data type of the aggregate function's internal transition (state) data</entry>
       </row>
       <row>
        <entry><structfield>agginitval</structfield></entry>
--- 370,394 ----
        <entry><structfield>aggtranstype</structfield></entry>
        <entry><type>oid</type></entry>
        <entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
!       <entry>Data type of the aggregate function's internal transition (state) data (zero if none)</entry>
!      </row>
!      <row>
!       <entry><structfield>aggtranssortop</structfield></entry>
!       <entry><type>oid</type></entry>
!       <entry><literal><link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.oid</literal></entry>
!       <entry>An optional sort operator for the type "aggtranstype", used for some kinds of ordered set functions</entry>
!      </row>
!      <row>
!       <entry><structfield>aggordnargs</structfield></entry>
!       <entry><type>int4</type></entry>
!       <entry></entry>
!       <entry>Number of direct arguments to ordered set function; -2 for hypothetical set functions; -1 for ordinary aggregates.</entry>
!      </row>
!      <row>
!       <entry><structfield>aggisordsetfunc</structfield></entry>
!       <entry><type>bool</type></entry>
!       <entry></entry>
!       <entry>A flag to represent whether a function is ordered set or not</entry>
       </row>
       <row>
        <entry><structfield>agginitval</structfield></entry>
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 12197,12202 **** SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab;
--- 12197,12453 ----
  
   </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>
  
*** a/doc/src/sgml/ref/alter_aggregate.sgml
--- b/doc/src/sgml/ref/alter_aggregate.sgml
***************
*** 21,32 **** PostgreSQL documentation
  
   <refsynopsisdiv>
  <synopsis>
! ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
    RENAME TO <replaceable>new_name</replaceable>
  ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
    OWNER TO <replaceable>new_owner</replaceable>
  ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
    SET SCHEMA <replaceable>new_schema</replaceable>
  </synopsis>
   </refsynopsisdiv>
  
--- 21,37 ----
  
   <refsynopsisdiv>
  <synopsis>
! ALTER AGGREGATE
    RENAME TO <replaceable>new_name</replaceable>
  ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
    OWNER TO <replaceable>new_owner</replaceable>
  ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
    SET SCHEMA <replaceable>new_schema</replaceable>
+ 
+ <phrase>where <replaceable>aggregate_signature</replaceable> is one of:</phrase>
+ 
+ <replaceable>name</replaceable> ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
+ <replaceable>name</replaceable> ( [ [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] ] ) WITHIN GROUP ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
  </synopsis>
   </refsynopsisdiv>
  
***************
*** 148,157 **** ALTER AGGREGATE myavg(integer) OWNER TO joe;
    </para>
  
    <para>
!    To move the aggregate function <literal>myavg</literal> for type
!    <type>integer</type> into schema <literal>myschema</literal>:
  <programlisting>
! ALTER AGGREGATE myavg(integer) SET SCHEMA myschema;
  </programlisting></para>
   </refsect1>
  
--- 153,163 ----
    </para>
  
    <para>
!    To move the ordered set function <literal>mypercentile</literal> with
!    direct argument of type <type>float8</type> taking groups
!    of <type>integer</type> type into schema <literal>myschema</literal>:
  <programlisting>
! ALTER AGGREGATE mypercentile(float8) WITHIN GROUP (integer) SET SCHEMA myschema;
  </programlisting></para>
   </refsect1>
  
*** a/doc/src/sgml/ref/alter_extension.sgml
--- b/doc/src/sgml/ref/alter_extension.sgml
***************
*** 30,36 **** ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> DROP <replacea
  
  <phrase>where <replaceable class="PARAMETER">member_object</replaceable> is:</phrase>
  
!   AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) |
    CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
    COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
    CONVERSION <replaceable class="PARAMETER">object_name</replaceable> |
--- 30,36 ----
  
  <phrase>where <replaceable class="PARAMETER">member_object</replaceable> is:</phrase>
  
!   AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) [ WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) ] |
    CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
    COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
    CONVERSION <replaceable class="PARAMETER">object_name</replaceable> |
*** a/doc/src/sgml/ref/comment.sgml
--- b/doc/src/sgml/ref/comment.sgml
***************
*** 23,29 **** PostgreSQL documentation
  <synopsis>
  COMMENT ON
  {
!   AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) |
    CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
    COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
    COLUMN <replaceable class="PARAMETER">relation_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
--- 23,29 ----
  <synopsis>
  COMMENT ON
  {
!   AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) [ WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) ] |
    CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
    COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
    COLUMN <replaceable class="PARAMETER">relation_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
*** a/doc/src/sgml/ref/create_aggregate.sgml
--- b/doc/src/sgml/ref/create_aggregate.sgml
***************
*** 29,34 **** CREATE AGGREGATE <replaceable class="parameter">name</replaceable> ( [ <replacea
--- 29,43 ----
      [ , 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,76 **** CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
    </para>
  
    <para>
!    An aggregate function is made from one or two ordinary
     functions:
     a state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
--- 79,85 ----
    </para>
  
    <para>
!    An ordinary aggregate function is made from one or two ordinary
     functions:
     a state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
***************
*** 165,170 **** SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
--- 174,187 ----
    </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,283 **** SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
--- 295,305 ----
        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,310 **** SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
--- 327,365 ----
       </para>
      </listitem>
     </varlistentry>
+ 
+    <varlistentry>
+     <term><replaceable class="PARAMETER">state_sort_operator</replaceable></term>
+     <listitem>
+      <para>
+       For ordered set functions only, this is a sort operator that can be
+       applied to
+       the <replaceable class="PARAMETER">state_data_type</replaceable>.
+       This is just an operator name (possibly schema-qualified).
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>STRICT</literal></term>
+     <listitem>
+      <para>
+       For ordered set functions only, this flag specifies that the function is
+       strict, i.e. that grouped rows containing nulls are skipped.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>HYPOTHETICAL</literal></term>
+     <listitem>
+      <para>
+       For ordered set functions only, this flag specifies that the aggregate
+       parameters are to be processed according to the requirements for
+       hypothetical set functions.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
  
    <para>
*** a/doc/src/sgml/ref/drop_aggregate.sgml
--- b/doc/src/sgml/ref/drop_aggregate.sgml
***************
*** 21,29 **** PostgreSQL documentation
  
   <refsynopsisdiv>
  <synopsis>
! DROP AGGREGATE [ IF EXISTS ]
!   <replaceable class="parameter">name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">arg_name</replaceable> ] <replaceable class="parameter">arg_data_type</replaceable> [ , ... ] )
    [ CASCADE | RESTRICT ]
  </synopsis>
   </refsynopsisdiv>
  
--- 21,33 ----
  
   <refsynopsisdiv>
  <synopsis>
! DROP AGGREGATE [ IF EXISTS ] <replaceable class="parameter">aggregate_signature</replaceable>
    [ CASCADE | RESTRICT ]
+ 
+ <phrase>where <replaceable>aggregate_signature</replaceable> is one of:</phrase>
+ 
+ <replaceable>name</replaceable> ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
+ <replaceable>name</replaceable> ( [ [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] ] ) WITHIN GROUP ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
  </synopsis>
   </refsynopsisdiv>
  
*** a/doc/src/sgml/ref/security_label.sgml
--- b/doc/src/sgml/ref/security_label.sgml
***************
*** 25,31 **** SECURITY LABEL [ FOR <replaceable class="PARAMETER">provider</replaceable> ] ON
  {
    TABLE <replaceable class="PARAMETER">object_name</replaceable> |
    COLUMN <replaceable class="PARAMETER">table_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
!   AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) |
    DATABASE <replaceable class="PARAMETER">object_name</replaceable> |
    DOMAIN <replaceable class="PARAMETER">object_name</replaceable> |
    EVENT TRIGGER <replaceable class="PARAMETER">object_name</replaceable> |
--- 25,31 ----
  {
    TABLE <replaceable class="PARAMETER">object_name</replaceable> |
    COLUMN <replaceable class="PARAMETER">table_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
!   AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) [ WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) ] |
    DATABASE <replaceable class="PARAMETER">object_name</replaceable> |
    DOMAIN <replaceable class="PARAMETER">object_name</replaceable> |
    EVENT TRIGGER <replaceable class="PARAMETER">object_name</replaceable> |
*** a/doc/src/sgml/syntax.sgml
--- b/doc/src/sgml/syntax.sgml
***************
*** 1706,1711 **** SELECT string_agg(a ORDER BY a, ',') FROM table;  -- incorrect
--- 1706,1759 ----
     </para>
    </sect2>
  
+   <sect2 id="syntax-orderedset">
+    <title>Ordered Set Functions</title>
+ 
+    <indexterm zone="syntax-orderedset">
+     <primary>ordered set function</primary>
+    </indexterm>
+ 
+    <indexterm zone="syntax-orderedset">
+     <primary>aggregate function</primary>
+     <secondary>ordered set function</secondary>
+    </indexterm>
+ 
+    <indexterm zone="syntax-orderedset">
+     <primary>WITHIN GROUP</primary>
+    </indexterm>
+ 
+    <para>
+     An <firstterm>ordered set function</firstterm> is a particular kind of
+     aggregate function which is applied to sorted groups of values and returns
+     a single result for each group which may be influenced by the sort
+     order. Like all aggregate functions, it reduces multiple inputs to a
+     single output value; typical ordered set functions return a percentile
+     extracted from the ordered group, or the rank a specified value would have
+     within that group.  The syntax of an ordered set function is:
+ 
+ <synopsis>
+ <replaceable>function_name</replaceable> ( [ <replaceable>expression</replaceable> [ , ... ] ] ) WITHIN GROUP ( <replaceable>order_by_clause</replaceable> ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+ </synopsis>
+ 
+     where <replaceable>function_name</replaceable> is a previously
+     defined ordered set function (possibly qualified with a schema name) and
+     <replaceable>expression</replaceable> is any value expression that does
+     not itself contain an aggregate expression, a window function call, or any
+     reference to ungrouped columns of the source data.  The
+     mandatory <replaceable>order_by_clause</replaceable> has the same syntax
+     as for a query-level <literal>ORDER BY</> clause, as described
+     in <xref linkend="queries-order">, except that its expressions are always
+     just expressions and cannot be output-column names or numbers.  The
+     expressions of the <replaceable>order_by_clause</replaceable> may, and
+     almost invariably do, refer to the ungrouped columns of the input; the
+     clause defines the grouped input to the function. The optional
+     <replaceable>filter_clause</replaceable> is identical to that for
+     aggregate functions (see <xref linkend="syntax-aggregates">, and is applied
+     to input rows prior to the sort operation.
+    </para>
+ 
+   </sect2>
+ 
    <sect2 id="syntax-window-functions">
     <title>Window Function Calls</title>
  
*** a/doc/src/sgml/xaggr.sgml
--- b/doc/src/sgml/xaggr.sgml
***************
*** 9,28 ****
    </indexterm>
  
    <para>
!    Aggregate functions  in <productname>PostgreSQL</productname>
!    are expressed in terms of <firstterm>state values</firstterm>
!    and <firstterm>state transition functions</firstterm>.
!    That is, an aggregate operates using a state value that is updated
!    as each successive input row is processed.
!    To define a new aggregate
!    function, one selects a data type for the state value,
!    an initial value for the state, and a state transition
!    function.  The state transition function is just an
!    ordinary function that could also be used outside the
!    context of the aggregate.  A <firstterm>final function</firstterm>
!    can also be specified, in case the desired result of the aggregate
!    is different from the data that needs to be kept in the running
!    state value.
    </para>
  
    <para>
--- 9,25 ----
    </indexterm>
  
    <para>
!    Aggregate functions (other than ordered set functions)
!    in <productname>PostgreSQL</productname> are expressed in terms
!    of <firstterm>state values</firstterm> and <firstterm>state transition
!    functions</firstterm>.  That is, an aggregate operates using a state value
!    that is updated as each successive input row is processed.  To define a new
!    aggregate function, one selects a data type for the state value, an initial
!    value for the state, and a state transition function.  The state transition
!    function is just an ordinary function that could also be used outside the
!    context of the aggregate.  A <firstterm>final function</firstterm> can also
!    be specified, in case the desired result of the aggregate is different from
!    the data that needs to be kept in the running state value.
    </para>
  
    <para>
*** a/src/backend/catalog/pg_aggregate.c
--- b/src/backend/catalog/pg_aggregate.c
***************
*** 46,51 **** Oid
--- 46,52 ----
  AggregateCreate(const char *aggName,
  				Oid aggNamespace,
  				int numArgs,
+ 				int numDirectArgs,
  				oidvector *parameterTypes,
  				Datum allParameterTypes,
  				Datum parameterModes,
***************
*** 54,77 **** AggregateCreate(const char *aggName,
  				List *aggtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
! 				const char *agginitval)
  {
  	Relation	aggdesc;
  	HeapTuple	tup;
  	bool		nulls[Natts_pg_aggregate];
  	Datum		values[Natts_pg_aggregate];
  	Form_pg_proc proc;
! 	Oid			transfn;
  	Oid			finalfn = InvalidOid;	/* can be omitted */
  	Oid			sortop = InvalidOid;	/* can be omitted */
  	Oid		   *aggArgTypes = parameterTypes->values;
  	bool		hasPolyArg;
  	bool		hasInternalArg;
  	Oid			rettype;
  	Oid			finaltype;
! 	Oid		   *fnArgs;
! 	int			nargs_transfn;
  	Oid			procOid;
  	TupleDesc	tupDesc;
  	int			i;
--- 55,83 ----
  				List *aggtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
+ 				List *aggtranssortopName,
  				Oid aggTransType,
! 				const char *agginitval,
! 				bool isStrict,
! 				bool isOrderedSet,
! 				bool isHypotheticalSet)
  {
  	Relation	aggdesc;
  	HeapTuple	tup;
  	bool		nulls[Natts_pg_aggregate];
  	Datum		values[Natts_pg_aggregate];
  	Form_pg_proc proc;
! 	Oid			transfn = InvalidOid;	/* can be omitted */
  	Oid			finalfn = InvalidOid;	/* can be omitted */
  	Oid			sortop = InvalidOid;	/* can be omitted */
+ 	Oid			transsortop = InvalidOid;  /* Can be omitted */
  	Oid		   *aggArgTypes = parameterTypes->values;
  	bool		hasPolyArg;
  	bool		hasInternalArg;
+ 	Oid         variadic_type = InvalidOid;
  	Oid			rettype;
  	Oid			finaltype;
! 	Oid		   *fnArgs = palloc((numArgs + 1) * sizeof(Oid));
  	Oid			procOid;
  	TupleDesc	tupDesc;
  	int			i;
***************
*** 83,90 **** AggregateCreate(const char *aggName,
  	if (!aggName)
  		elog(ERROR, "no aggregate name supplied");
  
! 	if (!aggtransfnName)
! 		elog(ERROR, "aggregate must have a transition function");
  
  	/* check for polymorphic and INTERNAL arguments */
  	hasPolyArg = false;
--- 89,108 ----
  	if (!aggName)
  		elog(ERROR, "no aggregate name supplied");
  
! 	if (isOrderedSet)
! 	{
! 		if (aggtransfnName)
! 			elog(ERROR, "Ordered set functions cannot have transition functions");
! 		if (!aggfinalfnName)
! 			elog(ERROR, "Ordered set functions must have final functions");
! 	}
! 	else
! 	{
! 		if (!aggtransfnName)
! 			elog(ERROR, "aggregate must have a transition function");
! 		if (isStrict)
! 			elog(ERROR, "aggregate with transition function must not be explicitly STRICT");
! 	}
  
  	/* check for polymorphic and INTERNAL arguments */
  	hasPolyArg = false;
***************
*** 97,102 **** AggregateCreate(const char *aggName,
--- 115,250 ----
  			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,159 **** AggregateCreate(const char *aggName,
  				 errmsg("cannot determine transition data type"),
  				 errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument.")));
  
! 	/* find the transfn */
! 	nargs_transfn = numArgs + 1;
! 	fnArgs = (Oid *) palloc(nargs_transfn * sizeof(Oid));
! 	fnArgs[0] = aggTransType;
! 	memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
! 	transfn = lookup_agg_function(aggtransfnName, nargs_transfn, fnArgs,
! 								  &rettype);
  
! 	/*
! 	 * Return type of transfn (possibly after refinement by
! 	 * enforce_generic_type_consistency, if transtype isn't polymorphic) must
! 	 * exactly match declared transtype.
! 	 *
! 	 * In the non-polymorphic-transtype case, it might be okay to allow a
! 	 * rettype that's binary-coercible to transtype, but I'm not quite
! 	 * convinced that it's either safe or useful.  When transtype is
! 	 * polymorphic we *must* demand exact equality.
! 	 */
! 	if (rettype != aggTransType)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_DATATYPE_MISMATCH),
! 				 errmsg("return type of transition function %s is not %s",
! 						NameListToString(aggtransfnName),
! 						format_type_be(aggTransType))));
  
! 	tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn));
! 	if (!HeapTupleIsValid(tup))
! 		elog(ERROR, "cache lookup failed for function %u", transfn);
! 	proc = (Form_pg_proc) GETSTRUCT(tup);
  
! 	/*
! 	 * If the transfn is strict and the initval is NULL, make sure first input
! 	 * type and transtype are the same (or at least binary-compatible), so
! 	 * that it's OK to use the first input value as the initial transValue.
! 	 */
! 	if (proc->proisstrict && agginitval == NULL)
! 	{
! 		if (numArgs < 1 ||
! 			!IsBinaryCoercible(aggArgTypes[0], aggTransType))
  			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
  	}
- 	ReleaseSysCache(tup);
  
  	/* handle finalfn, if supplied */
! 	if (aggfinalfnName)
  	{
  		fnArgs[0] = aggTransType;
  		finalfn = lookup_agg_function(aggfinalfnName, 1, fnArgs,
--- 255,340 ----
  				 errmsg("cannot determine transition data type"),
  				 errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument.")));
  
! 	if (!isOrderedSet)
! 	{
! 		/* find the transfn */		
  
! 		fnArgs[0] = aggTransType;
! 		memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
  
! 		transfn = lookup_agg_function(aggtransfnName, numArgs + 1, fnArgs,
! 									  &rettype);
  
! 		/*
! 		 * Return type of transfn (possibly after refinement by
! 		 * enforce_generic_type_consistency, if transtype isn't polymorphic)
! 		 * must exactly match declared transtype.
! 		 *
! 		 * In the non-polymorphic-transtype case, it might be okay to allow a
! 		 * rettype that's binary-coercible to transtype, but I'm not quite
! 		 * convinced that it's either safe or useful.  When transtype is
! 		 * polymorphic we *must* demand exact equality.
! 		 */
! 		if (rettype != aggTransType)
  			ereport(ERROR,
! 					(errcode(ERRCODE_DATATYPE_MISMATCH),
! 					 errmsg("return type of transition function %s is not %s",
! 							NameListToString(aggtransfnName),
! 							format_type_be(aggTransType))));
! 
! 		tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn));
! 		if (!HeapTupleIsValid(tup))
! 			elog(ERROR, "cache lookup failed for function %u", transfn);
! 		proc = (Form_pg_proc) GETSTRUCT(tup);
! 
! 		/*
! 	 	 * If the transfn is strict and the initval is NULL, make sure first
! 	 	 * input type and transtype are the same (or at least
! 	 	 * binary-compatible), so that it's OK to use the first input value as
! 	 	 * the initial transValue.
! 	 	 */
! 		if (proc->proisstrict && agginitval == NULL)
! 		{
! 			if (numArgs < 1 ||
! 				!IsBinaryCoercible(aggArgTypes[0], aggTransType))
! 				ereport(ERROR,
! 						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 						 errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
! 		}
! 		ReleaseSysCache(tup);
  	}
  
  	/* handle finalfn, if supplied */
! 	if (isOrderedSet)
! 	{
! 		int num_final_args = numArgs;
! 
! 		memcpy(fnArgs, aggArgTypes, num_final_args * sizeof(Oid));
! 
! 		/*
! 		 * If there's a transtype, it becomes the last arg to the finalfn;
! 		 * but if the agg (and hence the finalfn) is variadic "any", then
! 		 * this contributes nothing to the signature.
! 		 */
! 		if (aggTransType != InvalidOid && variadic_type != ANYOID)
! 			fnArgs[num_final_args++] = aggTransType;
! 
! 		finalfn = lookup_agg_function(aggfinalfnName, num_final_args, fnArgs,
! 									  &finaltype);
! 
! 		/*
! 		 * this is also checked at runtime for security reasons, but check
! 		 * here too to provide a friendly error (the requirement is because
! 		 * the finalfn will be passed null dummy args for type resolution
! 		 * purposes)
! 		 */
! 
! 		if (func_strict(finalfn))
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("ordered set final functions must not be declared STRICT")));
! 	}
! 	else if (aggfinalfnName)
  	{
  		fnArgs[0] = aggTransType;
  		finalfn = lookup_agg_function(aggfinalfnName, 1, fnArgs,
***************
*** 166,171 **** AggregateCreate(const char *aggName,
--- 347,353 ----
  		 */
  		finaltype = aggTransType;
  	}
+ 
  	Assert(OidIsValid(finaltype));
  
  	/*
***************
*** 207,212 **** AggregateCreate(const char *aggName,
--- 389,406 ----
  								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,231 **** AggregateCreate(const char *aggName,
  			aclcheck_error_type(aclresult, aggArgTypes[i]);
  	}
  
! 	aclresult = pg_type_aclcheck(aggTransType, GetUserId(), ACL_USAGE);
! 	if (aclresult != ACLCHECK_OK)
! 		aclcheck_error_type(aclresult, aggTransType);
  
  	aclresult = pg_type_aclcheck(finaltype, GetUserId(), ACL_USAGE);
  	if (aclresult != ACLCHECK_OK)
  		aclcheck_error_type(aclresult, finaltype);
  
- 
  	/*
  	 * Everything looks okay.  Try to create the pg_proc entry for the
  	 * aggregate.  (This could fail if there's already a conflicting entry.)
--- 411,427 ----
  			aclcheck_error_type(aclresult, aggArgTypes[i]);
  	}
  
! 	if (OidIsValid(aggTransType))
! 	{
! 		aclresult = pg_type_aclcheck(aggTransType, GetUserId(), ACL_USAGE);
! 		if (aclresult != ACLCHECK_OK)
! 			aclcheck_error_type(aclresult, aggTransType);
! 	}
  
  	aclresult = pg_type_aclcheck(finaltype, GetUserId(), ACL_USAGE);
  	if (aclresult != ACLCHECK_OK)
  		aclcheck_error_type(aclresult, finaltype);
  
  	/*
  	 * Everything looks okay.  Try to create the pg_proc entry for the
  	 * aggregate.  (This could fail if there's already a conflicting entry.)
***************
*** 246,252 **** AggregateCreate(const char *aggName,
  							  false,	/* security invoker (currently not
  										 * definable for agg) */
  							  false,	/* isLeakProof */
! 							  false,	/* isStrict (not needed for agg) */
  							  PROVOLATILE_IMMUTABLE,	/* volatility (not
  														 * needed for agg) */
  							  parameterTypes,	/* paramTypes */
--- 442,448 ----
  							  false,	/* security invoker (currently not
  										 * definable for agg) */
  							  false,	/* isLeakProof */
! 							  isStrict,	/* isStrict (needed for ordered set funcs) */
  							  PROVOLATILE_IMMUTABLE,	/* volatility (not
  														 * needed for agg) */
  							  parameterTypes,	/* paramTypes */
***************
*** 272,278 **** AggregateCreate(const char *aggName,
--- 468,478 ----
  	values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
  	values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
  	values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
+ 	values[Anum_pg_aggregate_aggtranssortop - 1] = ObjectIdGetDatum(transsortop);
  	values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
+ 	values[Anum_pg_aggregate_aggordnargs - 1] = Int32GetDatum(numDirectArgs);
+ 	values[Anum_pg_aggregate_aggisordsetfunc - 1] = BoolGetDatum(isOrderedSet);
+ 
  	if (agginitval)
  		values[Anum_pg_aggregate_agginitval - 1] = CStringGetTextDatum(agginitval);
  	else
***************
*** 290,307 **** AggregateCreate(const char *aggName,
  
  	/*
  	 * Create dependencies for the aggregate (above and beyond those already
! 	 * made by ProcedureCreate).  Note: we don't need an explicit dependency
! 	 * on aggTransType since we depend on it indirectly through transfn.
  	 */
  	myself.classId = ProcedureRelationId;
  	myself.objectId = procOid;
  	myself.objectSubId = 0;
  
  	/* Depends on transition function */
! 	referenced.classId = ProcedureRelationId;
! 	referenced.objectId = transfn;
! 	referenced.objectSubId = 0;
! 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  
  	/* Depends on final function, if any */
  	if (OidIsValid(finalfn))
--- 490,512 ----
  
  	/*
  	 * Create dependencies for the aggregate (above and beyond those already
! 	 * made by ProcedureCreate).  Normal aggs don't need an explicit
! 	 * dependency on aggTransType since we depend on it indirectly through
! 	 * transfn, but ordered set functions with variadic "any" do need one
! 	 * (ordered set functions without variadic depend on it via the finalfn).
  	 */
  	myself.classId = ProcedureRelationId;
  	myself.objectId = procOid;
  	myself.objectSubId = 0;
  
  	/* Depends on transition function */
! 	if (OidIsValid(transfn))
! 	{
! 		referenced.classId = ProcedureRelationId;
! 		referenced.objectId = transfn;
! 		referenced.objectSubId = 0;
! 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
! 	}
  
  	/* Depends on final function, if any */
  	if (OidIsValid(finalfn))
***************
*** 321,326 **** AggregateCreate(const char *aggName,
--- 526,549 ----
  		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  	}
  
+ 	/* Depends on transsort operator, if any */
+ 	if (OidIsValid(transsortop))
+ 	{
+ 		referenced.classId = OperatorRelationId;
+ 		referenced.objectId = transsortop;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
+ 	/* May depend on aggTransType if any */
+ 	if (OidIsValid(aggTransType) && isOrderedSet && variadic_type == ANYOID)
+ 	{
+ 		referenced.classId = TypeRelationId;
+ 		referenced.objectId = aggTransType;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	return procOid;
  }
  
*** a/src/backend/commands/aggregatecmds.c
--- b/src/backend/commands/aggregatecmds.c
***************
*** 44,52 ****
   *	DefineAggregate
   *
   * "oldstyle" signals the old (pre-8.2) style where the aggregate input type
!  * is specified by a BASETYPE element in the parameters.  Otherwise,
!  * "args" is a list of FunctionParameter structs defining the agg's arguments.
!  * "parameters" is a list of DefElem representing the agg's definition clauses.
   */
  Oid
  DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
--- 44,55 ----
   *	DefineAggregate
   *
   * "oldstyle" signals the old (pre-8.2) style where the aggregate input type
!  * is specified by a BASETYPE element in the parameters.  Otherwise, "args" is
!  * a pair, whose first element is a list of FunctionParameter structs defining
!  * the agg's arguments (both direct and ordered), and whose second element is
!  * an Integer node with the number of direct args, or -1 if this isn't an
!  * ordered set func.  "parameters" is a list of DefElem representing the agg's
!  * definition clauses.
   */
  Oid
  DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
***************
*** 58,75 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  	List	   *transfuncName = NIL;
  	List	   *finalfuncName = NIL;
  	List	   *sortoperatorName = NIL;
  	TypeName   *baseType = NULL;
  	TypeName   *transType = NULL;
  	char	   *initval = NULL;
  	int			numArgs;
  	oidvector  *parameterTypes;
  	ArrayType  *allParameterTypes;
  	ArrayType  *parameterModes;
  	ArrayType  *parameterNames;
  	List	   *parameterDefaults;
- 	Oid			transTypeId;
  	char		transTypeType;
  	ListCell   *pl;
  
  	/* Convert list of names to a name and namespace */
  	aggNamespace = QualifiedNameGetCreationNamespace(name, &aggName);
--- 61,83 ----
  	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;
  	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,85 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
--- 88,101 ----
  		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,111 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
--- 122,133 ----
  			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,129 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  							defel->defname)));
  	}
  
! 	/*
! 	 * make sure we have our required definitions
! 	 */
! 	if (transType == NULL)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 				 errmsg("aggregate stype must be specified")));
! 	if (transfuncName == NIL)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 				 errmsg("aggregate sfunc must be specified")));
  
  	/*
  	 * look up the aggregate's input datatype(s).
--- 135,169 ----
  							defel->defname)));
  	}
  
! 	if (!isOrderedSet)
! 	{
! 		/*
! 		 * make sure we have our required definitions
! 		 */
! 		if (transType == NULL)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 						errmsg("aggregate stype must be specified")));
! 		if (transfuncName == NIL)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("aggregate sfunc must be specified")));
! 		if (isStrict)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("aggregate with sfunc must not be explicitly declared STRICT")));
! 	}
! 	else
! 	{
! 		if (transfuncName != NIL)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("sfunc must not be specified for ordered set functions")));
! 		if (finalfuncName == NIL)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("finalfunc must be specified for ordered set functions")));
! 	}
  
  	/*
  	 * look up the aggregate's input datatype(s).
***************
*** 173,180 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  					 errmsg("basetype is redundant with aggregate input type specification")));
  
! 		numArgs = list_length(args);
! 		interpret_function_parameter_list(args,
  										  InvalidOid,
  										  true, /* is an aggregate */
  										  queryString,
--- 213,227 ----
  					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  					 errmsg("basetype is redundant with aggregate input type specification")));
  
! 		/*
! 		 * The grammar has already concatenated the direct and ordered
! 		 * args (if any) for us. Note that error checking for position
! 		 * and number of VARIADIC args is not done for us, we have to
! 		 * do it ourselves later (in AggregateCreate)
! 		 */
! 
! 		numArgs = list_length(linitial(args));
! 		interpret_function_parameter_list(linitial(args),
  										  InvalidOid,
  										  true, /* is an aggregate */
  										  queryString,
***************
*** 191,197 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  	}
  
  	/*
! 	 * look up the aggregate's transtype.
  	 *
  	 * transtype can't be a pseudo-type, since we need to be able to store
  	 * values of the transtype.  However, we can allow polymorphic transtype
--- 238,244 ----
  	}
  
  	/*
! 	 * 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,218 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  	 * worse) by connecting up incompatible internal-using functions in an
  	 * aggregate.
  	 */
! 	transTypeId = typenameTypeId(NULL, transType);
! 	transTypeType = get_typtype(transTypeId);
! 	if (transTypeType == TYPTYPE_PSEUDO &&
! 		!IsPolymorphicType(transTypeId))
  	{
! 		if (transTypeId == INTERNALOID && superuser())
! 			 /* okay */ ;
! 		else
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					 errmsg("aggregate transition data type cannot be %s",
! 							format_type_be(transTypeId))));
  	}
  
  	/*
--- 248,267 ----
  	 * worse) by connecting up incompatible internal-using functions in an
  	 * aggregate.
  	 */
! 	if (transType)
  	{
! 		transTypeId = typenameTypeId(NULL, transType);
! 		transTypeType = get_typtype(transTypeId);
! 		if (transTypeType == TYPTYPE_PSEUDO &&
! 			!IsPolymorphicType(transTypeId))
! 		{
! 			if (transTypeId != INTERNALOID || !superuser() || isOrderedSet)
! 				ereport(ERROR,
! 						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 						 errmsg("aggregate transition data type cannot be %s",
! 								format_type_be(transTypeId))));
! 		}
! 
  	}
  
  	/*
***************
*** 224,236 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  	 * value.  However, if it's an incorrect value it seems much more
  	 * user-friendly to complain at CREATE AGGREGATE time.
  	 */
! 	if (initval && transTypeType != TYPTYPE_PSEUDO)
  	{
! 		Oid			typinput,
! 					typioparam;
  
! 		getTypeInputInfo(transTypeId, &typinput, &typioparam);
! 		(void) OidInputFunctionCall(typinput, initval, typioparam, -1);
  	}
  
  	/*
--- 273,295 ----
  	 * value.  However, if it's an incorrect value it seems much more
  	 * user-friendly to complain at CREATE AGGREGATE time.
  	 */
! 	if (transType)
  	{
! 		if (initval && transTypeType != TYPTYPE_PSEUDO)
! 		{
! 			Oid			typinput,
! 						typioparam;
  
! 			getTypeInputInfo(transTypeId, &typinput, &typioparam);
! 			(void) OidInputFunctionCall(typinput, initval, typioparam, -1);
! 		}
! 	}
! 	else
! 	{
! 		if (initval)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! 					errmsg("INITVAL must not be specified without STYPE")));
  	}
  
  	/*
***************
*** 239,244 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
--- 298,304 ----
  	return AggregateCreate(aggName,		/* aggregate name */
  						   aggNamespace,		/* namespace */
  						   numArgs,
+ 						   numDirectArgs,
  						   parameterTypes,
  						   PointerGetDatum(allParameterTypes),
  						   PointerGetDatum(parameterModes),
***************
*** 247,252 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
  						   transfuncName,		/* step function name */
  						   finalfuncName,		/* final function name */
  						   sortoperatorName,	/* sort operator name */
  						   transTypeId, /* transition data type */
! 						   initval);	/* initial condition */
  }
--- 307,316 ----
  						   transfuncName,		/* step function name */
  						   finalfuncName,		/* final function name */
  						   sortoperatorName,	/* sort operator name */
+ 						   transsortoperatorName,  /* transsort operator name */
  						   transTypeId, /* transition data type */
! 						   initval,  /* initial condition */
! 						   isStrict,  /* is explicitly STRICT */
! 						   isOrderedSet,  /* If the function is an ordered set */
! 						   ishypothetical);  /* If the function is a hypothetical set */
  }
*** a/src/backend/commands/functioncmds.c
--- b/src/backend/commands/functioncmds.c
***************
*** 274,281 **** interpret_function_parameter_list(List *parameters,
  		/* handle input parameters */
  		if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
  		{
! 			/* other input parameters can't follow a VARIADIC parameter */
! 			if (varCount > 0)
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  						 errmsg("VARIADIC parameter must be the last input parameter")));
--- 274,286 ----
  		/* handle input parameters */
  		if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
  		{
! 			/*
! 			 * For functions, other input parameters can't follow a VARIADIC
! 			 * parameter; for aggregates, we might be dealing with an ordered
! 			 * set function which have more complex rules for variadics, so
! 			 * punt the error checking for that case to the caller.
! 			 */
! 			if (varCount > 0 && !is_aggregate)
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  						 errmsg("VARIADIC parameter must be the last input parameter")));
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
***************
*** 4410,4415 **** ExecInitExpr(Expr *node, PlanState *parent)
--- 4410,4416 ----
  
  					astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
  														 parent);
+ 					astate->orddirectargs = (List *) ExecInitExpr((Expr *) aggref->orddirectargs, parent);
  					astate->aggfilter = ExecInitExpr(aggref->aggfilter,
  													 parent);
  
*** a/src/backend/executor/functions.c
--- b/src/backend/executor/functions.c
***************
*** 380,387 **** sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
  		param = ParseFuncOrColumn(pstate,
  								  list_make1(subfield),
  								  list_make1(param),
! 								  NIL, NULL, false, false, false,
! 								  NULL, true, cref->location);
  	}
  
  	return param;
--- 380,387 ----
  		param = ParseFuncOrColumn(pstate,
  								  list_make1(subfield),
  								  list_make1(param),
! 								  cref->location,
! 								  NULL);
  	}
  
  	return param;
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
***************
*** 3,9 ****
   * nodeAgg.c
   *	  Routines to handle aggregate nodes.
   *
!  *	  ExecAgg evaluates each aggregate in the following steps:
   *
   *		 transvalue = initcond
   *		 foreach input_tuple do
--- 3,9 ----
   * nodeAgg.c
   *	  Routines to handle aggregate nodes.
   *
!  *	  ExecAgg evaluates each normal aggregate in the following steps:
   *
   *		 transvalue = initcond
   *		 foreach input_tuple do
***************
*** 66,71 ****
--- 66,91 ----
   *	  AggState is available as context in earlier releases (back to 8.1),
   *	  but direct examination of the node is needed to use it before 9.0.
   *
+  *---
+  *
+  *    Ordered set functions modify the above process in a number of ways.
+  *    Most importantly, they do not have transfuncs at all; the same sort
+  *    mechanism used for ORDER BY/DISTINCT as described above is used to
+  *    process the input, but then the finalfunc is called without actually
+  *    running the sort (the finalfunc is allowed to insert rows first).
+  *    The finalfunc has access via a set of AggSet* API functions to the
+  *    Tuplesortstate, row count in the group, and other ancillary info.
+  *
+  *    Ordered set functions can, however, have a transvalue declared; this is
+  *    treated as a constant, and added to the end of the sort fields.
+  *    Hypothetical set functions use this to provide a flag that distinguishes
+  *    the hypothetical row from the input data.
+  *
+  *    Since they have no transfunc, ordered set functions have their own
+  *    'strict' flag stored in the aggregate's own pg_proc entry; this affects
+  *    whether rows containing nulls are placed in the sorter. But since we
+  *    pass dummy null arguments to the finalfunc for type resolution purposes,
+  *    no ordered set finalfunc is allowed to be strict.
   *
   * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
   * Portions Copyright (c) 1994, Regents of the University of California
***************
*** 87,96 ****
--- 107,118 ----
  #include "executor/nodeAgg.h"
  #include "miscadmin.h"
  #include "nodes/nodeFuncs.h"
+ #include "nodes/makefuncs.h"
  #include "optimizer/clauses.h"
  #include "optimizer/tlist.h"
  #include "parser/parse_agg.h"
  #include "parser/parse_coerce.h"
+ #include "parser/parse_clause.h"
  #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
***************
*** 105,110 ****
--- 127,134 ----
   */
  typedef struct AggStatePerAggData
  {
+ 	NodeTag type;
+ 	
  	/*
  	 * These values are set up during ExecInitAgg() and do not change
  	 * thereafter:
***************
*** 114,123 **** typedef struct AggStatePerAggData
  	AggrefExprState *aggrefstate;
  	Aggref	   *aggref;
  
! 	/* number of input arguments for aggregate function proper */
  	int			numArguments;
  
! 	/* number of inputs including ORDER BY expressions */
  	int			numInputs;
  
  	/* Oids of transfer functions */
--- 138,162 ----
  	AggrefExprState *aggrefstate;
  	Aggref	   *aggref;
  
! 	/* Pointer to parent AggState node */
! 	AggState   *aggstate;
! 
! 	/* copied from aggref */
! 	bool        isOrderedSet;
! 
! 	/*
! 	 * number of arguments for aggregate function proper.
! 	 * For ordered set functions, this includes the ORDER BY
! 	 * columns, *except* in the case of hypothetical set functions.
! 	 */
  	int			numArguments;
  
! 	/*
! 	 * number of inputs including ORDER BY expressions. For ordered
! 	 * set functions, *only* the ORDER BY expressions are included
! 	 * here, since the direct args to the function are not properly
! 	 * "input" in the sense of being derived from the tuple group.
! 	 */
  	int			numInputs;
  
  	/* Oids of transfer functions */
***************
*** 126,137 **** typedef struct AggStatePerAggData
  
  	/*
  	 * fmgr lookup data for transfer functions --- only valid when
! 	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
! 	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
  	FmgrInfo	finalfn;
  
  	/* Input collation derived for aggregate */
  	Oid			aggCollation;
  
--- 165,187 ----
  
  	/*
  	 * fmgr lookup data for transfer functions --- only valid when
! 	 * corresponding oid is not InvalidOid.
  	 */
  	FmgrInfo	transfn;
  	FmgrInfo	finalfn;
  
+ 	/*
+ 	 * If >0, aggregate as a whole is strict (skips null input)
+ 	 * The value specifies how many columns to check; normal aggs
+ 	 * only check numArguments, while ordered set functions check
+ 	 * numInputs.
+ 	 *
+ 	 * Ordered set functions are not allowed to have strict finalfns;
+ 	 * other aggregates respect the finalfn strict flag in the
+ 	 * FmgrInfo above.
+ 	 */
+ 	int         numStrict;
+ 
  	/* Input collation derived for aggregate */
  	Oid			aggCollation;
  
***************
*** 148,153 **** typedef struct AggStatePerAggData
--- 198,206 ----
  	Oid		   *sortCollations;
  	bool	   *sortNullsFirst;
  
+ 	/* just for convenience of ordered set funcs, not used here */
+ 	Oid		   *sortEqOperators;
+ 
  	/*
  	 * fmgr lookup data for input columns' equality operators --- only
  	 * set/used when aggregate has DISTINCT flag.  Note that these are in
***************
*** 204,209 **** typedef struct AggStatePerAggData
--- 257,265 ----
  	 */
  
  	Tuplesortstate *sortstate;	/* sort object, if DISTINCT or ORDER BY */
+ 
+ 	int64 number_of_rows;		/* number of rows */
+ 
  }	AggStatePerAggData;
  
  /*
***************
*** 300,305 **** initialize_aggregates(AggState *aggstate,
--- 356,363 ----
  		AggStatePerAgg peraggstate = &peragg[aggno];
  		AggStatePerGroup pergroupstate = &pergroup[aggno];
  
+ 		peraggstate->number_of_rows = 0;
+ 
  		/*
  		 * Start a fresh sort operation for each DISTINCT/ORDER BY aggregate.
  		 */
***************
*** 383,396 **** advance_transition_function(AggState *aggstate,
  	MemoryContext oldContext;
  	Datum		newVal;
  	int			i;
  
! 	if (peraggstate->transfn.fn_strict)
  	{
  		/*
  		 * For a strict transfn, nothing happens when there's a NULL input; we
  		 * just keep the prior transValue.
  		 */
! 		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
  				return;
--- 441,457 ----
  	MemoryContext oldContext;
  	Datum		newVal;
  	int			i;
+ 	int         numStrict = peraggstate->numStrict;
  
! 	Assert(OidIsValid(peraggstate->transfn_oid));
! 
! 	if (numStrict > 0)
  	{
  		/*
  		 * For a strict transfn, nothing happens when there's a NULL input; we
  		 * just keep the prior transValue.
  		 */
! 		for (i = 1; i <= numStrict; i++)
  		{
  			if (fcinfo->argnull[i])
  				return;
***************
*** 506,529 **** advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
  
  		if (peraggstate->numSortCols > 0)
  		{
  			/* DISTINCT and/or ORDER BY case */
  			Assert(slot->tts_nvalid == peraggstate->numInputs);
  
  			/*
! 			 * If the transfn is strict, we want to check for nullity before
  			 * storing the row in the sorter, to save space if there are a lot
! 			 * of nulls.  Note that we must only check numArguments columns,
! 			 * not numInputs, since nullity in columns used only for sorting
! 			 * is not relevant here.
  			 */
! 			if (peraggstate->transfn.fn_strict)
  			{
! 				for (i = 0; i < nargs; i++)
  				{
  					if (slot->tts_isnull[i])
  						break;
  				}
! 				if (i < nargs)
  					continue;
  			}
  
--- 567,590 ----
  
  		if (peraggstate->numSortCols > 0)
  		{
+ 			int numStrict = peraggstate->numStrict;
+ 
  			/* DISTINCT and/or ORDER BY case */
  			Assert(slot->tts_nvalid == peraggstate->numInputs);
  
  			/*
! 			 * If the aggregate is strict, we want to check for nullity before
  			 * storing the row in the sorter, to save space if there are a lot
! 			 * of nulls.
  			 */
! 			if (numStrict > 0)
  			{
! 				for (i = 0; i < numStrict; i++)
  				{
  					if (slot->tts_isnull[i])
  						break;
  				}
! 				if (i < numStrict)
  					continue;
  			}
  
***************
*** 534,539 **** advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
--- 595,602 ----
  								   slot->tts_isnull[0]);
  			else
  				tuplesort_puttupleslot(peraggstate->sortstate, slot);
+ 
+ 			peraggstate->number_of_rows++;
  		}
  		else
  		{
***************
*** 756,770 **** finalize_aggregate(AggState *aggstate,
  	if (OidIsValid(peraggstate->finalfn_oid))
  	{
  		FunctionCallInfoData fcinfo;
  
! 		InitFunctionCallInfoData(fcinfo, &(peraggstate->finalfn), 1,
! 								 peraggstate->aggCollation,
! 								 (void *) aggstate, NULL);
! 		fcinfo.arg[0] = pergroupstate->transValue;
! 		fcinfo.argnull[0] = pergroupstate->transValueIsNull;
! 		if (fcinfo.flinfo->fn_strict && pergroupstate->transValueIsNull)
  		{
! 			/* don't call a strict function with NULL inputs */
  			*resultVal = (Datum) 0;
  			*resultIsNull = true;
  		}
--- 819,884 ----
  	if (OidIsValid(peraggstate->finalfn_oid))
  	{
  		FunctionCallInfoData fcinfo;
+ 		bool isnull = false;
+ 
+ 		if (!(peraggstate->isOrderedSet))
+ 		{
+ 			InitFunctionCallInfoData(fcinfo,
+ 									 &(peraggstate->finalfn),
+ 									 1,
+ 									 peraggstate->aggCollation,
+ 									 (void *) aggstate,
+ 									 NULL);
+ 
+ 			fcinfo.arg[0] = pergroupstate->transValue;
+ 			fcinfo.argnull[0] = isnull = pergroupstate->transValueIsNull;
+ 		}
+ 		else
+ 		{
+ 			List     *args = peraggstate->aggrefstate->orddirectargs;
+ 			ListCell *lc;
+ 			int       i = 0;
+ 			int       numArguments = peraggstate->numArguments;
+ 
+ 			ExecClearTuple(peraggstate->evalslot);
+ 			ExecClearTuple(peraggstate->uniqslot);
+ 
+ 			InitFunctionCallInfoData(fcinfo,
+ 									 &(peraggstate->finalfn),
+ 									 peraggstate->numArguments,
+ 									 peraggstate->aggCollation,
+ 									 (void *) peraggstate,
+ 									 NULL);
+ 
+ 			foreach (lc, args)
+ 			{
+ 				ExprState *expr = (ExprState *) lfirst(lc);
+ 
+ 				fcinfo.arg[i] = ExecEvalExpr(expr,
+ 											 aggstate->ss.ps.ps_ExprContext,
+ 											 &fcinfo.argnull[i],
+ 											 NULL);
+ 				if (fcinfo.argnull[i])
+ 					isnull = true;
  
! 				++i;
! 			}
! 
! 			for(; i < numArguments; i++)
! 			{
! 				fcinfo.arg[i] = (Datum) 0;
! 				fcinfo.argnull[i] = true;
! 				isnull = true;
! 			}
! 		}
! 
! 		if (isnull && fcinfo.flinfo->fn_strict)
  		{
! 			/*
! 			 * don't call a strict function with NULL inputs; for ordered set
! 			 * functions this is paranoia, we already required that fn_strict
! 			 * is false, but easy to check anyway
! 			 */
  			*resultVal = (Datum) 0;
  			*resultIsNull = true;
  		}
***************
*** 1164,1169 **** agg_retrieve_direct(AggState *aggstate)
--- 1278,1294 ----
  		}
  
  		/*
+ 		 * Use the representative input tuple for any references to
+ 		 * non-aggregated input columns in the qual and tlist.	(If we are not
+ 		 * grouping, and there are no input rows at all, we will come here
+ 		 * with an empty firstSlot ... but if not grouping, there can't be any
+ 		 * references to non-aggregated input columns, so no problem.)
+ 		 * We do this before finalizing because for ordered set functions,
+ 		 * finalize_aggregates can evaluate arguments referencing the tuple.
+ 		 */
+ 		econtext->ecxt_outertuple = firstSlot;
+ 
+ 		/*
  		 * Done scanning input tuple group. Finalize each aggregate
  		 * calculation, and stash results in the per-output-tuple context.
  		 */
***************
*** 1174,1187 **** agg_retrieve_direct(AggState *aggstate)
  
  			if (peraggstate->numSortCols > 0)
  			{
! 				if (peraggstate->numInputs == 1)
! 					process_ordered_aggregate_single(aggstate,
! 													 peraggstate,
! 													 pergroupstate);
! 				else
! 					process_ordered_aggregate_multi(aggstate,
! 													peraggstate,
! 													pergroupstate);
  			}
  
  			finalize_aggregate(aggstate, peraggstate, pergroupstate,
--- 1299,1315 ----
  
  			if (peraggstate->numSortCols > 0)
  			{
! 				if (!(peraggstate->isOrderedSet))
! 				{
! 					if (peraggstate->numInputs == 1)
! 						process_ordered_aggregate_single(aggstate,
! 														 peraggstate,
! 														 pergroupstate);
! 					else
! 						process_ordered_aggregate_multi(aggstate,
! 														peraggstate,
! 														pergroupstate);
! 				}
  			}
  
  			finalize_aggregate(aggstate, peraggstate, pergroupstate,
***************
*** 1189,1203 **** agg_retrieve_direct(AggState *aggstate)
  		}
  
  		/*
- 		 * Use the representative input tuple for any references to
- 		 * non-aggregated input columns in the qual and tlist.	(If we are not
- 		 * grouping, and there are no input rows at all, we will come here
- 		 * with an empty firstSlot ... but if not grouping, there can't be any
- 		 * references to non-aggregated input columns, so no problem.)
- 		 */
- 		econtext->ecxt_outertuple = firstSlot;
- 
- 		/*
  		 * Check the qual (HAVING clause); if the group does not match, ignore
  		 * it and loop back to try to process another group.
  		 */
--- 1317,1322 ----
***************
*** 1568,1577 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
--- 1687,1698 ----
  		int			numInputs;
  		int			numSortCols;
  		int			numDistinctCols;
+ 		bool        isOrderedSet = aggref->isordset;
  		List	   *sortlist;
  		HeapTuple	aggTuple;
  		Form_pg_aggregate aggform;
  		Oid			aggtranstype;
+ 		Oid			aggtranstypecoll;
  		AclResult	aclresult;
  		Oid			transfn_oid,
  					finalfn_oid;
***************
*** 1580,1585 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
--- 1701,1710 ----
  		Datum		textInitVal;
  		int			i;
  		ListCell   *lc;
+         bool        is_strict;
+ 		Oid			inputCollations[FUNC_MAX_ARGS];
+ 		List       *argexprs;
+ 		List       *argexprstate;
  
  		/* Planner should have assigned aggregate to correct level */
  		Assert(aggref->agglevelsup == 0);
***************
*** 1601,1631 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  		/* Nope, so assign a new PerAgg record */
  		peraggstate = &peragg[++aggno];
  
- 		/* Mark Aggref state node with assigned index in the result array */
- 		aggrefstate->aggno = aggno;
- 
  		/* Fill in the peraggstate data */
! 		peraggstate->aggrefstate = aggrefstate;
  		peraggstate->aggref = aggref;
! 		numInputs = list_length(aggref->args);
! 		peraggstate->numInputs = numInputs;
! 		peraggstate->sortstate = NULL;
  
! 		/*
! 		 * Get actual datatypes of the inputs.	These could be different from
! 		 * the agg's declared input types, when the agg accepts ANY or a
! 		 * polymorphic type.
! 		 */
! 		numArguments = 0;
! 		foreach(lc, aggref->args)
! 		{
! 			TargetEntry *tle = (TargetEntry *) lfirst(lc);
  
! 			if (!tle->resjunk)
! 				inputTypes[numArguments++] = exprType((Node *) tle->expr);
! 		}
! 		peraggstate->numArguments = numArguments;
  
  		aggTuple = SearchSysCache1(AGGFNOID,
  								   ObjectIdGetDatum(aggref->aggfnoid));
  		if (!HeapTupleIsValid(aggTuple))
--- 1726,1743 ----
  		/* Nope, so assign a new PerAgg record */
  		peraggstate = &peragg[++aggno];
  
  		/* Fill in the peraggstate data */
! 		peraggstate->type = T_AggStatePerAggData;
! 		peraggstate->aggstate = aggstate;
  		peraggstate->aggref = aggref;
! 		peraggstate->aggrefstate = aggrefstate;
  
! 		peraggstate->isOrderedSet = isOrderedSet;
  
! 		/* Mark Aggref state node with assigned index in the result array */
! 		aggrefstate->aggno = aggno;
  
+ 		/* Fetch the pg_aggregate row */
  		aggTuple = SearchSysCache1(AGGFNOID,
  								   ObjectIdGetDatum(aggref->aggfnoid));
  		if (!HeapTupleIsValid(aggTuple))
***************
*** 1633,1638 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
--- 1745,1757 ----
  				 aggref->aggfnoid);
  		aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
  
+ 		/*
+ 		 * Check that the definition hasn't somehow changed incompatibly.
+ 		 */
+ 		if (isOrderedSet != (aggform->aggisordsetfunc)
+ 			|| (aggref->ishypothetical != (aggform->aggordnargs == -2)))
+ 			elog(ERROR, "Incompatible change to aggregate definition");
+ 
  		/* Check permission to call aggregate function */
  		aclresult = pg_proc_aclcheck(aggref->aggfnoid, GetUserId(),
  									 ACL_EXECUTE);
***************
*** 1644,1668 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  		peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
  		peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
  
! 		/* Check that aggregate owner has permission to call component fns */
  		{
  			HeapTuple	procTuple;
  			Oid			aggOwner;
  
  			procTuple = SearchSysCache1(PROCOID,
  										ObjectIdGetDatum(aggref->aggfnoid));
  			if (!HeapTupleIsValid(procTuple))
  				elog(ERROR, "cache lookup failed for function %u",
  					 aggref->aggfnoid);
! 			aggOwner = ((Form_pg_proc) GETSTRUCT(procTuple))->proowner;
  			ReleaseSysCache(procTuple);
  
! 			aclresult = pg_proc_aclcheck(transfn_oid, aggOwner,
! 										 ACL_EXECUTE);
! 			if (aclresult != ACLCHECK_OK)
  				aclcheck_error(aclresult, ACL_KIND_PROC,
  							   get_func_name(transfn_oid));
! 			InvokeFunctionExecuteHook(transfn_oid);
  			if (OidIsValid(finalfn_oid))
  			{
  				aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
--- 1763,1799 ----
  		peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
  		peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
  
! 		/*
! 		 * Check that aggregate owner has permission to call component fns
! 		 * In passing, fetch the proisstrict flag for the aggregate proper,
! 		 * which subs for the transfn's strictness flag in cases where there
! 		 * is no transfn.
! 		 */
  		{
  			HeapTuple	procTuple;
  			Oid			aggOwner;
+ 			Form_pg_proc procp;
  
  			procTuple = SearchSysCache1(PROCOID,
  										ObjectIdGetDatum(aggref->aggfnoid));
  			if (!HeapTupleIsValid(procTuple))
  				elog(ERROR, "cache lookup failed for function %u",
  					 aggref->aggfnoid);
! 			procp = (Form_pg_proc) GETSTRUCT(procTuple);
! 			aggOwner = procp->proowner;
! 			is_strict = procp->proisstrict;
  			ReleaseSysCache(procTuple);
  
! 			if (OidIsValid(transfn_oid))
! 			{
! 				aclresult = pg_proc_aclcheck(transfn_oid, aggOwner,
! 									         ACL_EXECUTE);
! 				if (aclresult != ACLCHECK_OK && OidIsValid(transfn_oid))
  				aclcheck_error(aclresult, ACL_KIND_PROC,
  							   get_func_name(transfn_oid));
! 				InvokeFunctionExecuteHook(transfn_oid);
! 			}
! 
  			if (OidIsValid(finalfn_oid))
  			{
  				aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
***************
*** 1674,1690 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  			}
  		}
  
  		/* resolve actual type of transition state, if polymorphic */
  		aggtranstype = aggform->aggtranstype;
! 		if (IsPolymorphicType(aggtranstype))
  		{
  			/* have to fetch the agg's declared input types... */
  			Oid		   *declaredArgTypes;
! 			int			agg_nargs;
  
  			(void) get_func_signature(aggref->aggfnoid,
! 									  &declaredArgTypes, &agg_nargs);
! 			Assert(agg_nargs == numArguments);
  			aggtranstype = enforce_generic_type_consistency(inputTypes,
  															declaredArgTypes,
  															agg_nargs,
--- 1805,1841 ----
  			}
  		}
  
+ 		/*
+ 		 * Get actual datatypes of the inputs.	These could be different from
+ 		 * the agg's declared input types, when the agg accepts ANY or a
+ 		 * polymorphic type.
+ 		 */
+ 
+ 		peraggstate->numInputs = numInputs = list_length(aggref->args);
+ 
+ 		numArguments = get_aggregate_argtypes(aggref,
+ 											  inputTypes,
+ 											  inputCollations);
+ 
  		/* resolve actual type of transition state, if polymorphic */
  		aggtranstype = aggform->aggtranstype;
! 		if (OidIsValid(aggtranstype) && IsPolymorphicType(aggtranstype))
  		{
  			/* have to fetch the agg's declared input types... */
  			Oid		   *declaredArgTypes;
! 			int         agg_nargs;
  
  			(void) get_func_signature(aggref->aggfnoid,
! 									  &declaredArgTypes,
! 									  &agg_nargs);
! 
! 			/*
! 			 * if variadic "any", might be more actual args than declared
! 			 * args, but these extra args can't influence the determination
! 			 * of polymorphic transition or result type.
! 			 */
! 			Assert(agg_nargs <= numArguments);
! 
  			aggtranstype = enforce_generic_type_consistency(inputTypes,
  															declaredArgTypes,
  															agg_nargs,
***************
*** 1693,1727 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  			pfree(declaredArgTypes);
  		}
  
  		/* build expression trees using actual argument & result types */
! 		build_aggregate_fnexprs(inputTypes,
! 								numArguments,
! 								aggref->aggvariadic,
! 								aggtranstype,
! 								aggref->aggtype,
! 								aggref->inputcollid,
! 								transfn_oid,
! 								finalfn_oid,
! 								&transfnexpr,
! 								&finalfnexpr);
! 
! 		fmgr_info(transfn_oid, &peraggstate->transfn);
! 		fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
  
  		if (OidIsValid(finalfn_oid))
  		{
  			fmgr_info(finalfn_oid, &peraggstate->finalfn);
  			fmgr_info_set_expr((Node *) finalfnexpr, &peraggstate->finalfn);
  		}
  
  		peraggstate->aggCollation = aggref->inputcollid;
  
  		get_typlenbyval(aggref->aggtype,
  						&peraggstate->resulttypeLen,
  						&peraggstate->resulttypeByVal);
! 		get_typlenbyval(aggtranstype,
! 						&peraggstate->transtypeLen,
! 						&peraggstate->transtypeByVal);
  
  		/*
  		 * initval is potentially null, so don't try to access it as a struct
--- 1844,1925 ----
  			pfree(declaredArgTypes);
  		}
  
+ 		aggtranstypecoll = get_typcollation(aggtranstype);
+ 
  		/* build expression trees using actual argument & result types */
! 
! 		if (!isOrderedSet)
! 		{
! 			build_aggregate_fnexprs(inputTypes,
! 									numArguments,
! 									aggref->aggvariadic,
! 									aggtranstype,
! 									aggref->aggtype,
! 									aggref->inputcollid,
! 									transfn_oid,
! 									finalfn_oid,
! 									&transfnexpr,
! 									&finalfnexpr);
! 		}
! 		else
! 		{
! 			/*
! 			 * The transvalue counts as an argument, but not for hypothetical
! 			 * set funcs.
! 			 */
! 			if (OidIsValid(aggtranstype) && !(aggref->ishypothetical))
! 			{
! 				if (numArguments == FUNC_MAX_ARGS)
! 					elog(ERROR, "Too many arguments to ordered set function");
! 
! 				inputTypes[numArguments++] = aggtranstype;
! 				inputCollations[numArguments++] = aggtranstypecoll;
! 			}
! 
! 			build_orderedset_fnexprs(inputTypes,
! 									 numArguments,
! 									 aggref->aggvariadic,
! 									 aggref->aggtype,
! 									 aggref->inputcollid,
! 									 inputCollations,
! 									 finalfn_oid,
! 									 &finalfnexpr);
! 		}
! 
! 		peraggstate->numArguments = numArguments;
! 
! 		if (OidIsValid(transfn_oid))
! 		{
! 			fmgr_info(transfn_oid, &peraggstate->transfn);
! 			fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
! 
! 			is_strict = peraggstate->transfn.fn_strict;
! 		}
  
  		if (OidIsValid(finalfn_oid))
  		{
  			fmgr_info(finalfn_oid, &peraggstate->finalfn);
  			fmgr_info_set_expr((Node *) finalfnexpr, &peraggstate->finalfn);
+ 			if (peraggstate->finalfn.fn_strict && isOrderedSet)
+ 				elog(ERROR, "Ordered set finalfns must not be strict");
  		}
  
+ 		if (is_strict)
+ 			peraggstate->numStrict = (isOrderedSet ? numInputs : numArguments);
+ 		else
+ 			peraggstate->numStrict = 0;
+ 
  		peraggstate->aggCollation = aggref->inputcollid;
  
  		get_typlenbyval(aggref->aggtype,
  						&peraggstate->resulttypeLen,
  						&peraggstate->resulttypeByVal);
! 		if (OidIsValid(aggtranstype))
! 		{
! 			get_typlenbyval(aggtranstype,
! 							&peraggstate->transtypeLen,
! 							&peraggstate->transtypeByVal);
! 		}
  
  		/*
  		 * initval is potentially null, so don't try to access it as a struct
***************
*** 1744,1750 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  		 * transValue.	This should have been checked at agg definition time,
  		 * but just in case...
  		 */
! 		if (peraggstate->transfn.fn_strict && peraggstate->initValueIsNull)
  		{
  			if (numArguments < 1 ||
  				!IsBinaryCoercible(inputTypes[0], aggtranstype))
--- 1942,1950 ----
  		 * transValue.	This should have been checked at agg definition time,
  		 * but just in case...
  		 */
! 		if (OidIsValid(peraggstate->transfn_oid)
! 			&& peraggstate->transfn.fn_strict
! 			&& peraggstate->initValueIsNull)
  		{
  			if (numArguments < 1 ||
  				!IsBinaryCoercible(inputTypes[0], aggtranstype))
***************
*** 1754,1774 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  								aggref->aggfnoid)));
  		}
  
! 		/*
! 		 * Get a tupledesc corresponding to the inputs (including sort
! 		 * expressions) of the agg.
! 		 */
! 		peraggstate->evaldesc = ExecTypeFromTL(aggref->args, false);
! 
! 		/* Create slot we're going to do argument evaluation in */
! 		peraggstate->evalslot = ExecInitExtraTupleSlot(estate);
! 		ExecSetSlotDescriptor(peraggstate->evalslot, peraggstate->evaldesc);
! 
! 		/* Set up projection info for evaluation */
! 		peraggstate->evalproj = ExecBuildProjectionInfo(aggrefstate->args,
! 														aggstate->tmpcontext,
! 														peraggstate->evalslot,
! 														NULL);
  
  		/*
  		 * If we're doing either DISTINCT or ORDER BY, then we have a list of
--- 1954,1961 ----
  								aggref->aggfnoid)));
  		}
  
! 		argexprs = aggref->args;
! 		argexprstate = aggrefstate->args;
  
  		/*
  		 * If we're doing either DISTINCT or ORDER BY, then we have a list of
***************
*** 1777,1782 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
--- 1964,1974 ----
  		 *
  		 * Note that by construction, if there is a DISTINCT clause then the
  		 * ORDER BY clause is a prefix of it (see transformDistinctClause).
+ 		 *
+ 		 * If we're doing an ordered set function, though, we want to do the
+ 		 * initialization for DISTINCT since the ordered set finalfn might
+ 		 * want it, and it's much easier to do it here. So set numDistinctCols
+ 		 * and let the later initialization take care of it.
  		 */
  		if (aggref->aggdistinct)
  		{
***************
*** 1788,1798 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  		{
  			sortlist = aggref->aggorder;
  			numSortCols = list_length(sortlist);
! 			numDistinctCols = 0;
  		}
  
  		peraggstate->numSortCols = numSortCols;
  		peraggstate->numDistinctCols = numDistinctCols;
  
  		if (numSortCols > 0)
  		{
--- 1980,2065 ----
  		{
  			sortlist = aggref->aggorder;
  			numSortCols = list_length(sortlist);
! 			numDistinctCols = isOrderedSet ? numSortCols : 0;
! 		}
! 
! 		/*
! 		 * If this is an ordered set function, and we have a transtype, then
! 		 * it represents an extra column to be added to the sorter with a
! 		 * fixed value. Plus, if aggtranssortop is valid, we have to include
! 		 * a sort entry for the new column.
! 		 *
! 		 * I'd probably have done this in the planner if I'd seen any
! 		 * possible place to put it; if there is one, it's very obscure.
! 		 */
! 
! 		if (OidIsValid(aggtranstype) && isOrderedSet)
! 		{
! 			Oid sortop = aggform->aggtranssortop;
! 			Const *node = makeNode(Const);
! 			TargetEntry *tle;
! 			SortGroupClause *sortcl = NULL;
! 
! 			node->consttype = aggtranstype;
! 			node->consttypmod = -1;
! 			node->constcollid = aggtranstypecoll;
! 			node->constlen = peraggstate->transtypeLen;
! 			node->constvalue = peraggstate->initValue;
! 			node->constisnull = peraggstate->initValueIsNull;
! 			node->constbyval = peraggstate->transtypeByVal;
! 			node->location = -1;
! 
! 			tle = makeTargetEntry((Expr *) node,
! 								  ++numInputs,
! 								  NULL,
! 								  true);
! 
! 			peraggstate->numInputs = numInputs;
! 
! 			if (OidIsValid(sortop))
! 			{
! 				Assert(aggref->aggdistinct == NIL);
! 
! 				sortcl = makeNode(SortGroupClause);
! 
! 				sortcl->tleSortGroupRef = assignSortGroupRef(tle, argexprs);
! 
! 				sortcl->sortop = sortop;
! 				sortcl->hashable = false;
! 				sortcl->eqop = get_equality_op_for_ordering_op(sortop,
! 															   &sortcl->nulls_first);
! 
! 				sortlist = lappend(list_copy(sortlist), sortcl);
! 				++numSortCols;
! 				++numDistinctCols;
! 			}
! 
! 			/* shallow-copy the passed-in lists, which we must not scribble on. */
! 
! 			argexprs = lappend(list_copy(argexprs), (Node *) tle);
! 			argexprstate = lappend(list_copy(argexprstate),
! 								   ExecInitExpr((Expr *) tle, (PlanState *) aggstate));
  		}
  
  		peraggstate->numSortCols = numSortCols;
  		peraggstate->numDistinctCols = numDistinctCols;
+ 		peraggstate->sortstate = NULL;
+ 
+ 		/*
+ 		 * Get a tupledesc corresponding to the inputs (including sort
+ 		 * expressions) of the agg.
+ 		 */
+ 		peraggstate->evaldesc = ExecTypeFromTL(argexprs, false);
+ 
+ 		/* Create slot we're going to do argument evaluation in */
+ 		peraggstate->evalslot = ExecInitExtraTupleSlot(estate);
+ 		ExecSetSlotDescriptor(peraggstate->evalslot, peraggstate->evaldesc);
+ 
+ 		/* Set up projection info for evaluation */
+ 		peraggstate->evalproj = ExecBuildProjectionInfo(argexprstate,
+ 														aggstate->tmpcontext,
+ 														peraggstate->evalslot,
+ 														NULL);
  
  		if (numSortCols > 0)
  		{
***************
*** 1805,1815 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  			/* If we have only one input, we need its len/byval info. */
  			if (numInputs == 1)
  			{
! 				get_typlenbyval(inputTypes[0],
  								&peraggstate->inputtypeLen,
  								&peraggstate->inputtypeByVal);
  			}
! 			else if (numDistinctCols > 0)
  			{
  				/* we will need an extra slot to store prior values */
  				peraggstate->uniqslot = ExecInitExtraTupleSlot(estate);
--- 2072,2083 ----
  			/* If we have only one input, we need its len/byval info. */
  			if (numInputs == 1)
  			{
! 				get_typlenbyval(peraggstate->evaldesc->attrs[0]->atttypid,
  								&peraggstate->inputtypeLen,
  								&peraggstate->inputtypeByVal);
  			}
! 
! 			if (numDistinctCols > 0 && (numInputs > 1 || isOrderedSet))
  			{
  				/* we will need an extra slot to store prior values */
  				peraggstate->uniqslot = ExecInitExtraTupleSlot(estate);
***************
*** 1822,1871 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
  				(AttrNumber *) palloc(numSortCols * sizeof(AttrNumber));
  			peraggstate->sortOperators =
  				(Oid *) palloc(numSortCols * sizeof(Oid));
  			peraggstate->sortCollations =
  				(Oid *) palloc(numSortCols * sizeof(Oid));
  			peraggstate->sortNullsFirst =
  				(bool *) palloc(numSortCols * sizeof(bool));
  
  			i = 0;
  			foreach(lc, sortlist)
  			{
  				SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
  				TargetEntry *tle = get_sortgroupclause_tle(sortcl,
! 														   aggref->args);
  
  				/* the parser should have made sure of this */
  				Assert(OidIsValid(sortcl->sortop));
  
  				peraggstate->sortColIdx[i] = tle->resno;
  				peraggstate->sortOperators[i] = sortcl->sortop;
  				peraggstate->sortCollations[i] = exprCollation((Node *) tle->expr);
  				peraggstate->sortNullsFirst[i] = sortcl->nulls_first;
- 				i++;
- 			}
- 			Assert(i == numSortCols);
- 		}
  
! 		if (aggref->aggdistinct)
! 		{
! 			Assert(numArguments > 0);
! 
! 			/*
! 			 * We need the equal function for each DISTINCT comparison we will
! 			 * make.
! 			 */
! 			peraggstate->equalfns =
! 				(FmgrInfo *) palloc(numDistinctCols * sizeof(FmgrInfo));
! 
! 			i = 0;
! 			foreach(lc, aggref->aggdistinct)
! 			{
! 				SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
  
- 				fmgr_info(get_opcode(sortcl->eqop), &peraggstate->equalfns[i]);
  				i++;
  			}
! 			Assert(i == numDistinctCols);
  		}
  
  		ReleaseSysCache(aggTuple);
--- 2090,2136 ----
  				(AttrNumber *) palloc(numSortCols * sizeof(AttrNumber));
  			peraggstate->sortOperators =
  				(Oid *) palloc(numSortCols * sizeof(Oid));
+ 			peraggstate->sortEqOperators =
+ 				(Oid *) palloc(numSortCols * sizeof(Oid));
  			peraggstate->sortCollations =
  				(Oid *) palloc(numSortCols * sizeof(Oid));
  			peraggstate->sortNullsFirst =
  				(bool *) palloc(numSortCols * sizeof(bool));
+ 			if (numDistinctCols > 0)
+ 				peraggstate->equalfns =
+ 					(FmgrInfo *) palloc(numDistinctCols * sizeof(FmgrInfo));
+ 			else
+ 				peraggstate->equalfns = NULL;
+ 			
  
  			i = 0;
  			foreach(lc, sortlist)
  			{
  				SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
  				TargetEntry *tle = get_sortgroupclause_tle(sortcl,
! 														   argexprs);
  
  				/* the parser should have made sure of this */
  				Assert(OidIsValid(sortcl->sortop));
  
  				peraggstate->sortColIdx[i] = tle->resno;
  				peraggstate->sortOperators[i] = sortcl->sortop;
+ 				peraggstate->sortEqOperators[i] = sortcl->eqop;
  				peraggstate->sortCollations[i] = exprCollation((Node *) tle->expr);
  				peraggstate->sortNullsFirst[i] = sortcl->nulls_first;
  
! 				/*
! 				 * It's OK to get the equalfns here too, since we already
! 				 * require that sortlist is aggref->aggdistinct for the normal
! 				 * distinct case, and for ordered set functions using the
! 				 * (possibly modified copy of) aggref->aggorder is correct
! 				 */
! 				if (peraggstate->equalfns)
! 					fmgr_info(get_opcode(sortcl->eqop), &peraggstate->equalfns[i]);
  
  				i++;
  			}
! 			Assert(i == numSortCols);
  		}
  
  		ReleaseSysCache(aggTuple);
***************
*** 2023,2028 **** ExecReScanAgg(AggState *node)
--- 2288,2296 ----
   * If aggcontext isn't NULL, the function also stores at *aggcontext the
   * identity of the memory context that aggregate transition values are
   * being stored in.
+  *
+  * We do NOT include AGG_CONTEXT_ORDERED as a possible return here, since
+  * that would open a security hole.
   */
  int
  AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
***************
*** 2063,2065 **** aggregate_dummy(PG_FUNCTION_ARGS)
--- 2331,2448 ----
  		 fcinfo->flinfo->fn_oid);
  	return (Datum) 0;			/* keep compiler quiet */
  }
+ 
+ /* AggSetGetRowCount - Get the number of rows in case of ordered set
+  * functions.
+  */
+ int64
+ AggSetGetRowCount(FunctionCallInfo fcinfo)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ 	{
+ 		return ((AggStatePerAggData *)fcinfo->context)->number_of_rows;
+ 	}
+ 
+ 	elog(ERROR, "Called AggSetGetRowCount on non ordered set function");
+ 	return -1;
+ }
+ 
+ /* AggSetGetSortInfo - Get the sort state in the case of 
+  * ordered set functions.
+  */
+ void
+ AggSetGetSortInfo(FunctionCallInfo fcinfo,
+ 				  Tuplesortstate **sortstate,
+ 				  TupleDesc *tupdesc,
+ 				  TupleTableSlot **tupslot,
+ 				  Oid *datumtype)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ 	{
+ 		AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+ 
+ 		*sortstate = peraggstate->sortstate;
+ 		if (peraggstate->numInputs == 1)
+ 		{
+ 			if (tupdesc)
+ 				*tupdesc = NULL;
+ 			if (datumtype)
+ 				*datumtype = peraggstate->evaldesc->attrs[0]->atttypid;
+ 		}
+ 		else
+ 		{
+ 			if (tupdesc)
+ 				*tupdesc = peraggstate->evaldesc;
+ 			if (datumtype)
+ 				*datumtype = InvalidOid;
+ 		}
+ 		
+ 		if (tupslot)
+ 			*tupslot = peraggstate->evalslot;
+ 	}
+ 	else
+ 		elog(ERROR, "AggSetSortInfo called on non ordered set function");
+ }
+ 
+ int
+ AggSetGetDistinctInfo(FunctionCallInfo fcinfo,
+ 					  TupleTableSlot **uniqslot,
+ 					  AttrNumber **sortColIdx,
+ 					  FmgrInfo **equalfns)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ 	{
+ 		AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+ 
+ 		if (uniqslot)
+ 			*uniqslot = peraggstate->uniqslot;
+ 		if (sortColIdx)
+ 			*sortColIdx = peraggstate->sortColIdx;
+ 		if (equalfns)
+ 			*equalfns = peraggstate->equalfns;
+ 
+ 		return peraggstate->numDistinctCols;
+ 	}
+ 	else
+ 		elog(ERROR, "AggSetGetDistinctOperators called on non ordered set function");
+ }
+ 
+ int
+ AggSetGetSortOperators(FunctionCallInfo fcinfo,
+ 					   AttrNumber **sortColIdx,
+ 					   Oid **sortOperators,
+ 					   Oid **sortEqOperators,
+ 					   Oid **sortCollations,
+ 					   bool **sortNullsFirst)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ 	{
+ 		AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+ 
+ 		if (sortColIdx)
+ 			*sortColIdx = peraggstate->sortColIdx;
+ 		if (sortOperators)
+ 			*sortOperators = peraggstate->sortOperators;
+ 		if (sortEqOperators)
+ 			*sortEqOperators = peraggstate->sortEqOperators;
+ 		if (sortCollations)
+ 			*sortCollations = peraggstate->sortCollations;
+ 		if (sortNullsFirst)
+ 			*sortNullsFirst = peraggstate->sortNullsFirst;
+ 
+ 		return peraggstate->numSortCols;
+ 	}
+ 	else
+ 		elog(ERROR, "AggSetGetSortOperators called on non ordered set function");
+ }
+ 
+ void
+ AggSetGetPerTupleContext(FunctionCallInfo fcinfo,
+ 						 MemoryContext *memcontext)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ 	{
+ 		AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+ 		*memcontext = peraggstate->aggstate->tmpcontext->ecxt_per_tuple_memory;
+ 	}
+ }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1139,1147 **** _copyAggref(const Aggref *from)
--- 1139,1150 ----
  	COPY_NODE_FIELD(args);
  	COPY_NODE_FIELD(aggorder);
  	COPY_NODE_FIELD(aggdistinct);
+ 	COPY_NODE_FIELD(orddirectargs);
  	COPY_NODE_FIELD(aggfilter);
  	COPY_SCALAR_FIELD(aggstar);
  	COPY_SCALAR_FIELD(aggvariadic);
+ 	COPY_SCALAR_FIELD(isordset);
+ 	COPY_SCALAR_FIELD(ishypothetical);
  	COPY_SCALAR_FIELD(agglevelsup);
  	COPY_LOCATION_FIELD(location);
  
***************
*** 2174,2179 **** _copyFuncCall(const FuncCall *from)
--- 2177,2183 ----
  	COPY_SCALAR_FIELD(agg_star);
  	COPY_SCALAR_FIELD(agg_distinct);
  	COPY_SCALAR_FIELD(func_variadic);
+ 	COPY_SCALAR_FIELD(has_within_group);
  	COPY_NODE_FIELD(over);
  	COPY_LOCATION_FIELD(location);
  
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 196,204 **** _equalAggref(const Aggref *a, const Aggref *b)
--- 196,207 ----
  	COMPARE_NODE_FIELD(args);
  	COMPARE_NODE_FIELD(aggorder);
  	COMPARE_NODE_FIELD(aggdistinct);
+ 	COMPARE_NODE_FIELD(orddirectargs);
  	COMPARE_NODE_FIELD(aggfilter);
  	COMPARE_SCALAR_FIELD(aggstar);
  	COMPARE_SCALAR_FIELD(aggvariadic);
+ 	COMPARE_SCALAR_FIELD(isordset);
+ 	COMPARE_SCALAR_FIELD(ishypothetical);
  	COMPARE_SCALAR_FIELD(agglevelsup);
  	COMPARE_LOCATION_FIELD(location);
  
***************
*** 2006,2011 **** _equalFuncCall(const FuncCall *a, const FuncCall *b)
--- 2009,2015 ----
  	COMPARE_SCALAR_FIELD(agg_star);
  	COMPARE_SCALAR_FIELD(agg_distinct);
  	COMPARE_SCALAR_FIELD(func_variadic);
+ 	COMPARE_SCALAR_FIELD(has_within_group);
  	COMPARE_NODE_FIELD(over);
  	COMPARE_LOCATION_FIELD(location);
  
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
***************
*** 558,563 **** makeFuncCall(List *name, List *args, int location)
--- 558,564 ----
  	n->agg_star = FALSE;
  	n->agg_distinct = FALSE;
  	n->func_variadic = FALSE;
+ 	n->has_within_group = FALSE;
  	n->over = NULL;
  	return n;
  }
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 1631,1636 **** expression_tree_walker(Node *node,
--- 1631,1641 ----
  				if (expression_tree_walker((Node *) expr->aggdistinct,
  										   walker, context))
  					return true;
+ 
+ 				if (expression_tree_walker((Node *) expr->orddirectargs,
+ 										   walker, context))
+ 					return true;
+ 
  				if (walker((Node *) expr->aggfilter, context))
  					return true;
  			}
***************
*** 2155,2161 **** expression_tree_mutator(Node *node,
--- 2160,2168 ----
  				MUTATE(newnode->args, aggref->args, List *);
  				MUTATE(newnode->aggorder, aggref->aggorder, List *);
  				MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
+ 				MUTATE(newnode->orddirectargs, aggref->orddirectargs, List *);
  				MUTATE(newnode->aggfilter, aggref->aggfilter, Expr *);
+ 
  				return (Node *) newnode;
  			}
  			break;
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 960,968 **** _outAggref(StringInfo str, const Aggref *node)
--- 960,971 ----
  	WRITE_NODE_FIELD(args);
  	WRITE_NODE_FIELD(aggorder);
  	WRITE_NODE_FIELD(aggdistinct);
+ 	WRITE_NODE_FIELD(orddirectargs);
  	WRITE_NODE_FIELD(aggfilter);
  	WRITE_BOOL_FIELD(aggstar);
  	WRITE_BOOL_FIELD(aggvariadic);
+ 	WRITE_BOOL_FIELD(isordset);
+ 	WRITE_BOOL_FIELD(ishypothetical);
  	WRITE_UINT_FIELD(agglevelsup);
  	WRITE_LOCATION_FIELD(location);
  }
***************
*** 2090,2095 **** _outFuncCall(StringInfo str, const FuncCall *node)
--- 2093,2099 ----
  	WRITE_BOOL_FIELD(agg_star);
  	WRITE_BOOL_FIELD(agg_distinct);
  	WRITE_BOOL_FIELD(func_variadic);
+ 	WRITE_BOOL_FIELD(has_within_group);
  	WRITE_NODE_FIELD(over);
  	WRITE_LOCATION_FIELD(location);
  }
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 495,503 **** _readAggref(void)
--- 495,506 ----
  	READ_NODE_FIELD(args);
  	READ_NODE_FIELD(aggorder);
  	READ_NODE_FIELD(aggdistinct);
+ 	READ_NODE_FIELD(orddirectargs);
  	READ_NODE_FIELD(aggfilter);
  	READ_BOOL_FIELD(aggstar);
  	READ_BOOL_FIELD(aggvariadic);
+ 	READ_BOOL_FIELD(isordset);
+ 	READ_BOOL_FIELD(ishypothetical);
  	READ_UINT_FIELD(agglevelsup);
  	READ_LOCATION_FIELD(location);
  
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
***************
*** 39,44 ****
--- 39,45 ----
  #include "parser/analyze.h"
  #include "parser/parse_coerce.h"
  #include "parser/parse_func.h"
+ #include "parser/parse_agg.h"
  #include "rewrite/rewriteManip.h"
  #include "tcop/tcopprot.h"
  #include "utils/acl.h"
***************
*** 464,470 **** count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
  		QualCost	argcosts;
  		Oid		   *inputTypes;
  		int			numArguments;
- 		ListCell   *l;
  
  		Assert(aggref->agglevelsup == 0);
  
--- 465,470 ----
***************
*** 486,492 **** count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
  			costs->numOrderedAggs++;
  
  		/* add component function execution costs to appropriate totals */
! 		costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost;
  		if (OidIsValid(aggfinalfn))
  			costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost;
  
--- 486,493 ----
  			costs->numOrderedAggs++;
  
  		/* add component function execution costs to appropriate totals */
! 		if (OidIsValid(aggtransfn))
! 			costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost;
  		if (OidIsValid(aggfinalfn))
  			costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost;
  
***************
*** 504,575 **** count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
  		costs->transCost.startup += argcosts.startup;
  		costs->transCost.per_tuple += argcosts.per_tuple;
  
! 		/* extract argument types (ignoring any ORDER BY expressions) */
! 		inputTypes = (Oid *) palloc(sizeof(Oid) * list_length(aggref->args));
! 		numArguments = 0;
! 		foreach(l, aggref->args)
  		{
! 			TargetEntry *tle = (TargetEntry *) lfirst(l);
  
! 			if (!tle->resjunk)
! 				inputTypes[numArguments++] = exprType((Node *) tle->expr);
! 		}
  
! 		/* resolve actual type of transition state, if polymorphic */
! 		if (IsPolymorphicType(aggtranstype))
! 		{
! 			/* have to fetch the agg's declared input types... */
! 			Oid		   *declaredArgTypes;
! 			int			agg_nargs;
! 
! 			(void) get_func_signature(aggref->aggfnoid,
! 									  &declaredArgTypes, &agg_nargs);
! 			Assert(agg_nargs == numArguments);
! 			aggtranstype = enforce_generic_type_consistency(inputTypes,
! 															declaredArgTypes,
! 															agg_nargs,
! 															aggtranstype,
! 															false);
! 			pfree(declaredArgTypes);
! 		}
  
! 		/*
! 		 * If the transition type is pass-by-value then it doesn't add
! 		 * anything to the required size of the hashtable.	If it is
! 		 * pass-by-reference then we have to add the estimated size of the
! 		 * value itself, plus palloc overhead.
! 		 */
! 		if (!get_typbyval(aggtranstype))
! 		{
! 			int32		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);
  
! 			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;
  		}
  
  		/*
--- 505,595 ----
  		costs->transCost.startup += argcosts.startup;
  		costs->transCost.per_tuple += argcosts.per_tuple;
  
! 		/*
! 		 * If we're doing a sorted agg, we can punt the entire
! 		 * determination of transition element size since we're not
! 		 * going to be using it to determine hashtable limits. This
! 		 * simplifies the code for hypothetical set functions.
! 		 */
! 
! 		if (aggref->aggorder == NIL && aggref->aggdistinct == NIL)
  		{
! 			Assert(!aggref->isordset);
  
! 			/* extract argument types (ignoring any ORDER BY expressions) */
! 			inputTypes = (Oid *) palloc(sizeof(Oid) * FUNC_MAX_ARGS);
  
! 			numArguments = get_aggregate_argtypes(aggref, inputTypes, NULL);
  
! 			/* resolve actual type of transition state, if polymorphic */
! 			if (OidIsValid(aggtranstype) && IsPolymorphicType(aggtranstype))
! 			{
! 				/* have to fetch the agg's declared input types... */
! 				Oid		   *declaredArgTypes;
! 				int			agg_nargs;
! 
! 				(void) get_func_signature(aggref->aggfnoid,
! 										  &declaredArgTypes, &agg_nargs);
! 
! 				/*
! 				 * if variadic "any", might be more actual args than declared
! 				 * args, but these extra args can't influence the determination
! 				 * of polymorphic transition or result type.
! 				 */
! 				Assert(agg_nargs <= numArguments);
! 
! 				aggtranstype = enforce_generic_type_consistency(inputTypes,
! 																declaredArgTypes,
! 																agg_nargs,
! 																aggtranstype,
! 																false);
! 				pfree(declaredArgTypes);
! 			}
  
  			/*
! 			 * If the transition type is pass-by-value then it doesn't add
! 			 * anything to the required size of the hashtable.	If it is
! 			 * pass-by-reference then we have to add the estimated size of the
! 			 * value itself, plus palloc overhead.
  			 */
! 			if (OidIsValid(aggtranstype) && !get_typbyval(aggtranstype))
! 			{
! 				int32		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);
  
! 				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
  		{
! 			costs->transitionSpace = work_mem;   /* just in case */
  		}
  
  		/*
***************
*** 3826,3832 **** 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);
  }
  
  /*
--- 3846,3852 ----
  		elog(ERROR, "function's resolved result type changed during planning");
  
  	/* perform any necessary typecasting of arguments */
! 	make_fn_arguments(NULL, args, NULL, actual_arg_types, declared_arg_types, false);
  }
  
  /*
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 951,957 **** transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
  										  &qry->targetList,
  										  EXPR_KIND_ORDER_BY,
  										  true /* fix unknowns */ ,
! 										  false /* allow SQL92 rules */ );
  
  	qry->groupClause = transformGroupClause(pstate,
  											stmt->groupClause,
--- 951,958 ----
  										  &qry->targetList,
  										  EXPR_KIND_ORDER_BY,
  										  true /* fix unknowns */ ,
! 										  false /* allow SQL92 rules */,
! 										  false /* don't add duplicates */);
  
  	qry->groupClause = transformGroupClause(pstate,
  											stmt->groupClause,
***************
*** 1211,1217 **** transformValuesClause(ParseState *pstate, SelectStmt *stmt)
  										  &qry->targetList,
  										  EXPR_KIND_ORDER_BY,
  										  true /* fix unknowns */ ,
! 										  false /* allow SQL92 rules */ );
  
  	qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
  											EXPR_KIND_OFFSET, "OFFSET");
--- 1212,1219 ----
  										  &qry->targetList,
  										  EXPR_KIND_ORDER_BY,
  										  true /* fix unknowns */ ,
! 										  false /* allow SQL92 rules */,
! 										  false /* don't add duplicates */ );
  
  	qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
  											EXPR_KIND_OFFSET, "OFFSET");
***************
*** 1435,1441 **** transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
  										  &qry->targetList,
  										  EXPR_KIND_ORDER_BY,
  										  false /* no unknowns expected */ ,
! 										  false /* allow SQL92 rules */ );
  
  	/* restore namespace, remove jrte from rtable */
  	pstate->p_namespace = sv_namespace;
--- 1437,1444 ----
  										  &qry->targetList,
  										  EXPR_KIND_ORDER_BY,
  										  false /* no unknowns expected */ ,
! 										  false /* allow SQL92 rules */ ,
! 										  false /* don't add duplicates */ );
  
  	/* restore namespace, remove jrte from rtable */
  	pstate->p_namespace = sv_namespace;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 494,499 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
--- 494,500 ----
  %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,602 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
  	VERBOSE VERSION_P VIEW VOLATILE
  
! 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
  
  	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
  	XMLPI XMLROOT XMLSERIALIZE
--- 597,603 ----
  	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
  	VERBOSE VERSION_P VIEW VOLATILE
  
! 	WHEN WHERE WITHIN WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
  
  	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
  	XMLPI XMLROOT XMLSERIALIZE
***************
*** 3660,3666 **** AlterExtensionContentsStmt:
  					n->action = $4;
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $6;
! 					n->objargs = extractArgTypes($7);
  					$$ = (Node *)n;
  				}
  			| ALTER EXTENSION name add_drop CAST '(' Typename AS Typename ')'
--- 3661,3667 ----
  					n->action = $4;
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $6;
! 					n->objargs = extractArgTypes(linitial($7));
  					$$ = (Node *)n;
  				}
  			| ALTER EXTENSION name add_drop CAST '(' Typename AS Typename ')'
***************
*** 5239,5245 **** CommentStmt:
  					CommentStmt *n = makeNode(CommentStmt);
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $4;
! 					n->objargs = extractArgTypes($5);
  					n->comment = $7;
  					$$ = (Node *) n;
  				}
--- 5240,5246 ----
  					CommentStmt *n = makeNode(CommentStmt);
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $4;
! 					n->objargs = extractArgTypes(linitial($5));
  					n->comment = $7;
  					$$ = (Node *) n;
  				}
***************
*** 5405,5411 **** SecLabelStmt:
  					n->provider = $3;
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $6;
! 					n->objargs = extractArgTypes($7);
  					n->label = $9;
  					$$ = (Node *) n;
  				}
--- 5406,5412 ----
  					n->provider = $3;
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $6;
! 					n->objargs = extractArgTypes(linitial($7));
  					n->label = $9;
  					$$ = (Node *) n;
  				}
***************
*** 6405,6413 **** aggr_arg:	func_arg
  				}
  		;
  
! /* Zero-argument aggregates are named with * for consistency with COUNT(*) */
! aggr_args:	'(' aggr_args_list ')'					{ $$ = $2; }
! 			| '(' '*' ')'							{ $$ = NIL; }
  		;
  
  aggr_args_list:
--- 6406,6458 ----
  				}
  		;
  
! /*
!  * 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:
***************
*** 6613,6619 **** RemoveAggrStmt:
  					DropStmt *n = makeNode(DropStmt);
  					n->removeType = OBJECT_AGGREGATE;
  					n->objects = list_make1($3);
! 					n->arguments = list_make1(extractArgTypes($4));
  					n->behavior = $5;
  					n->missing_ok = false;
  					n->concurrent = false;
--- 6658,6664 ----
  					DropStmt *n = makeNode(DropStmt);
  					n->removeType = OBJECT_AGGREGATE;
  					n->objects = list_make1($3);
! 					n->arguments = list_make1(extractArgTypes(linitial($4)));
  					n->behavior = $5;
  					n->missing_ok = false;
  					n->concurrent = false;
***************
*** 6624,6630 **** RemoveAggrStmt:
  					DropStmt *n = makeNode(DropStmt);
  					n->removeType = OBJECT_AGGREGATE;
  					n->objects = list_make1($5);
! 					n->arguments = list_make1(extractArgTypes($6));
  					n->behavior = $7;
  					n->missing_ok = true;
  					n->concurrent = false;
--- 6669,6675 ----
  					DropStmt *n = makeNode(DropStmt);
  					n->removeType = OBJECT_AGGREGATE;
  					n->objects = list_make1($5);
! 					n->arguments = list_make1(extractArgTypes(linitial($6)));
  					n->behavior = $7;
  					n->missing_ok = true;
  					n->concurrent = false;
***************
*** 6840,6846 **** RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
  					RenameStmt *n = makeNode(RenameStmt);
  					n->renameType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes($4);
  					n->newname = $7;
  					n->missing_ok = false;
  					$$ = (Node *)n;
--- 6885,6891 ----
  					RenameStmt *n = makeNode(RenameStmt);
  					n->renameType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes(linitial($4));
  					n->newname = $7;
  					n->missing_ok = false;
  					$$ = (Node *)n;
***************
*** 7314,7320 **** AlterObjectSchemaStmt:
  					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
  					n->objectType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes($4);
  					n->newschema = $7;
  					n->missing_ok = false;
  					$$ = (Node *)n;
--- 7359,7365 ----
  					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
  					n->objectType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes(linitial($4));
  					n->newschema = $7;
  					n->missing_ok = false;
  					$$ = (Node *)n;
***************
*** 7543,7549 **** AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
  					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
  					n->objectType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes($4);
  					n->newowner = $7;
  					$$ = (Node *)n;
  				}
--- 7588,7594 ----
  					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
  					n->objectType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes(linitial($4));
  					n->newowner = $7;
  					$$ = (Node *)n;
  				}
***************
*** 9431,9436 **** sortby:		a_expr USING qual_all_Op opt_nulls_order
--- 9476,9486 ----
  		;
  
  
+ 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); }
***************
*** 11152,11163 **** func_application: func_name '(' ')'
   * (Note that many of the special SQL functions wouldn't actually make any
   * sense as functional index entries, but we ignore that consideration here.)
   */
! func_expr: func_application filter_clause over_clause 
  				{
!               		FuncCall *n = (FuncCall*)$1;
! 					n->agg_filter = $2;
! 					n->over = $3;
! 					$$ = (Node*)n;
  				} 
  			| func_expr_common_subexpr
  				{ $$ = $1; }
--- 11202,11236 ----
   * (Note that many of the special SQL functions wouldn't actually make any
   * sense as functional index entries, but we ignore that consideration here.)
   */
! func_expr: func_application within_group_clause filter_clause over_clause 
  				{
!               		FuncCall *n = (FuncCall *) $1;
! 					/*
! 					 * the order clause for WITHIN GROUP and the one
! 					 * for aggregate ORDER BY share a field, so we
! 					 * have to check here that at most one is present.
! 					 * We check for DISTINCT here to give a better
! 					 * error position.  Other consistency checks are
! 					 * deferred to parse_func.c or parse_agg.c
! 					 */
! 					if ($2 != NIL)
! 					{
! 						if (n->agg_order != NIL)
! 							ereport(ERROR,
! 									(errcode(ERRCODE_SYNTAX_ERROR),
! 									 errmsg("Cannot have multiple ORDER BY clauses with WITHIN GROUP"),
! 									 parser_errposition(@2)));
! 						if (n->agg_distinct)
! 							ereport(ERROR,
! 									(errcode(ERRCODE_SYNTAX_ERROR),
! 									 errmsg("Cannot have DISTINCT and WITHIN GROUP together"),
! 									 parser_errposition(@2)));
! 						n->agg_order = $2;
! 						n->has_within_group = TRUE;
! 					}
! 					n->agg_filter = $3;
! 					n->over = $4;
! 					$$ = (Node *) n;
  				} 
  			| func_expr_common_subexpr
  				{ $$ = $1; }
***************
*** 12716,12721 **** unreserved_keyword:
--- 12789,12795 ----
  			| VIEW
  			| VOLATILE
  			| WHITESPACE_P
+ 			| WITHIN
  			| WITHOUT
  			| WORK
  			| WRAPPER
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
***************
*** 44,50 **** typedef struct
  	int			sublevels_up;
  } check_ungrouped_columns_context;
  
! static int	check_agg_arguments(ParseState *pstate, List *args, Expr *filter);
  static bool check_agg_arguments_walker(Node *node,
  						   check_agg_arguments_context *context);
  static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
--- 44,52 ----
  	int			sublevels_up;
  } check_ungrouped_columns_context;
  
! static int	check_agg_arguments(ParseState *pstate, 
! 							List *args, 
! 							List *agg_ordset, Expr *filter);
  static bool check_agg_arguments_walker(Node *node,
  						   check_agg_arguments_context *context);
  static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
***************
*** 75,81 **** static bool check_ungrouped_columns_walker(Node *node,
   */
  void
  transformAggregateCall(ParseState *pstate, Aggref *agg,
! 					   List *args, List *aggorder, bool agg_distinct)
  {
  	List	   *tlist;
  	List	   *torder;
--- 77,84 ----
   */
  void
  transformAggregateCall(ParseState *pstate, Aggref *agg,
! 					   List *args, List *aggorder,
! 					   bool agg_distinct, bool agg_within_group)
  {
  	List	   *tlist;
  	List	   *torder;
***************
*** 93,104 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
  	 */
  	tlist = NIL;
  	attno = 1;
! 	foreach(lc, args)
  	{
! 		Expr	   *arg = (Expr *) lfirst(lc);
! 		TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false);
  
! 		tlist = lappend(tlist, tle);
  	}
  
  	/*
--- 96,119 ----
  	 */
  	tlist = NIL;
  	attno = 1;
! 
! 	if (agg_within_group)
  	{
! 		agg->isordset = TRUE;
! 		agg->orddirectargs = args;
! 	}
! 	else
! 	{
! 		foreach(lc, args)
! 		{
! 			Expr	   *arg = (Expr *) lfirst(lc);
! 			TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false);
  
! 			tlist = lappend(tlist, tle);
! 		}
! 
! 		agg->isordset = FALSE;
! 		agg->orddirectargs = NIL;
  	}
  
  	/*
***************
*** 109,114 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
--- 124,134 ----
  	 *
  	 * We need to mess with p_next_resno since it will be used to number any
  	 * new targetlist entries.
+ 	 *
+ 	 * If and only if we're doing a WITHIN GROUP list, we preserve any
+ 	 * duplicate expressions in the sort clause. This is needed because the
+ 	 * sort clause of WITHIN GROUP is really an argument list, and we must
+ 	 * keep the number and content of entries matching the specified input.
  	 */
  	save_next_resno = pstate->p_next_resno;
  	pstate->p_next_resno = attno;
***************
*** 118,124 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
  								 &tlist,
  								 EXPR_KIND_ORDER_BY,
  								 true /* fix unknowns */ ,
! 								 true /* force SQL99 rules */ );
  
  	/*
  	 * If we have DISTINCT, transform that to produce a distinctList.
--- 138,145 ----
  								 &tlist,
  								 EXPR_KIND_ORDER_BY,
  								 true /* fix unknowns */ ,
! 								 true /* force SQL99 rules */ ,
! 								 agg_within_group /* keep duplicates? */ );
  
  	/*
  	 * If we have DISTINCT, transform that to produce a distinctList.
***************
*** 160,166 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
  	 * Check the arguments to compute the aggregate's level and detect
  	 * improper nesting.
  	 */
! 	min_varlevel = check_agg_arguments(pstate, agg->args, agg->aggfilter);
  	agg->agglevelsup = min_varlevel;
  
  	/* Mark the correct pstate level as having aggregates */
--- 181,188 ----
  	 * Check the arguments to compute the aggregate's level and detect
  	 * improper nesting.
  	 */
! 	min_varlevel = check_agg_arguments(pstate, 
! 										agg->args, agg->orddirectargs, agg->aggfilter);
  	agg->agglevelsup = min_varlevel;
  
  	/* Mark the correct pstate level as having aggregates */
***************
*** 312,318 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
   * which we can't know until we finish scanning the arguments.
   */
  static int
! check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
  {
  	int			agglevel;
  	check_agg_arguments_context context;
--- 334,340 ----
   * which we can't know until we finish scanning the arguments.
   */
  static int
! check_agg_arguments(ParseState *pstate, List *args, List *agg_ordset, Expr *filter)
  {
  	int			agglevel;
  	check_agg_arguments_context context;
***************
*** 330,335 **** check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
--- 352,360 ----
  								  check_agg_arguments_walker,
  								  (void *) &context);
  
+ 	(void) expression_tree_walker((Node *) agg_ordset,	  check_agg_arguments_walker,
+ 								  (void *) &context);
+ 
  	/*
  	 * If we found no vars nor aggs at all, it's a level-zero aggregate;
  	 * otherwise, its level is the minimum of vars or aggs.
***************
*** 353,360 **** check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
  				(errcode(ERRCODE_GROUPING_ERROR),
  				 errmsg("aggregate function calls cannot be nested"),
  				 parser_errposition(pstate,
! 									locate_agg_of_level((Node *) args,
! 														agglevel))));
  
  	return agglevel;
  }
--- 378,385 ----
  				(errcode(ERRCODE_GROUPING_ERROR),
  				 errmsg("aggregate function calls cannot be nested"),
  				 parser_errposition(pstate,
! 								locate_agg_of_level((Node *) args,
! 								agglevel))));
  
  	return agglevel;
  }
***************
*** 823,830 **** check_ungrouped_columns_walker(Node *node,
  	 * We do need to look at aggregates of lower levels, however.
  	 */
  	if (IsA(node, Aggref) &&
! 		(int) ((Aggref *) node)->agglevelsup >= context->sublevels_up)
  		return false;
  
  	/*
  	 * If we have any GROUP BY items that are not simple Vars, check to see if
--- 848,863 ----
  	 * We do need to look at aggregates of lower levels, however.
  	 */
  	if (IsA(node, Aggref) &&
! 		(int) ((Aggref *) node)->agglevelsup > context->sublevels_up)
! 	{
  		return false;
+ 	}
+ 	else if (IsA(node, Aggref) &&
+ 			(int) ((Aggref *) node)->agglevelsup == context->sublevels_up)
+ 	{
+ 		return check_ungrouped_columns_walker((Node*)(((Aggref *)node)->orddirectargs), 
+ 														context);
+ 	}
  
  	/*
  	 * If we have any GROUP BY items that are not simple Vars, check to see if
***************
*** 1042,1044 **** build_aggregate_fnexprs(Oid *agg_input_types,
--- 1075,1172 ----
  										 agg_input_collation,
  										 COERCE_EXPLICIT_CALL);
  }
+ 
+ void
+ build_orderedset_fnexprs(Oid *agg_input_types,
+ 						 int agg_num_inputs,
+ 						 bool agg_variadic,
+ 						 Oid agg_result_type,
+ 						 Oid agg_input_collation,
+ 						 Oid *agg_input_collation_array,
+ 						 Oid finalfn_oid,
+ 						 Expr **finalfnexpr)
+ {
+ 	FuncExpr   *fexpr;
+ 	Param	   *argp;
+ 	List	   *args = NIL;
+ 	int			i = 0;
+ 
+ 	/*
+ 	 * Build arg list to use in the finalfn FuncExpr node. We really only care
+ 	 * that finalfn can discover the actual argument types at runtime using
+ 	 * get_fn_expr_argtype(), so it's okay to use Param nodes that don't
+ 	 * correspond to any real Param.
+ 	 */
+ 	for (i = 0; i < agg_num_inputs; i++)
+ 	{
+ 		argp = makeNode(Param);
+ 		argp->paramkind = PARAM_EXEC;
+ 		argp->paramid = -1;
+ 		argp->paramtype = agg_input_types[i];
+ 		argp->paramtypmod = -1;
+ 		argp->paramcollid = agg_input_collation_array[i];
+ 		argp->location = -1;
+ 
+ 		args = lappend(args, argp);
+ 	}
+ 
+ 	fexpr = makeFuncExpr(finalfn_oid,
+ 						 agg_result_type,
+ 						 args,
+ 						 InvalidOid,
+ 						 agg_input_collation,
+ 						 COERCE_EXPLICIT_CALL);
+ 	fexpr->funcvariadic = agg_variadic;
+ 	*finalfnexpr = (Expr *) fexpr;
+ }
+ 
+ int get_aggregate_argtypes(Aggref *aggref, Oid *inputTypes, Oid *inputCollations)
+ {
+ 	int numArguments = 0;
+ 	ListCell   *lc;
+ 
+ 	if (!(aggref->isordset))
+ 	{
+ 		foreach(lc, aggref->args)
+ 		{
+ 			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ 
+ 			if (!tle->resjunk)
+ 			{
+ 				inputTypes[numArguments] = exprType((Node *) tle->expr);
+ 				if (inputCollations != NULL)
+ 					inputCollations[numArguments] = exprCollation((Node *) tle->expr);
+ 				++numArguments;
+ 			}
+ 		}
+ 	}
+ 	else
+ 	{
+ 		foreach(lc, aggref->orddirectargs)
+ 		{
+ 			Node *expr_orddirectargs = lfirst(lc);
+ 
+ 			inputTypes[numArguments] = exprType(expr_orddirectargs);
+ 			if (inputCollations != NULL)
+ 				inputCollations[numArguments] = exprCollation(expr_orddirectargs);
+ 
+ 			++numArguments;
+ 		}
+ 
+ 		if (!(aggref->ishypothetical))
+ 		{
+ 			foreach(lc, aggref->args)
+ 			{
+ 				TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ 
+ 				inputTypes[numArguments] = exprType((Node *) tle->expr);
+ 				if (inputCollations != NULL)
+ 					inputCollations[numArguments] = exprCollation((Node *) tle->expr);
+ 
+ 				++numArguments;
+ 			}
+ 		}
+ 	}
+ 
+ 	return numArguments;
+ }
*** a/src/backend/parser/parse_clause.c
--- b/src/backend/parser/parse_clause.c
***************
*** 71,77 **** static void checkExprIsVarFree(ParseState *pstate, Node *n,
  static TargetEntry *findTargetlistEntrySQL92(ParseState *pstate, Node *node,
  						 List **tlist, ParseExprKind exprKind);
  static TargetEntry *findTargetlistEntrySQL99(ParseState *pstate, Node *node,
! 						 List **tlist, ParseExprKind exprKind);
  static int get_matching_location(int sortgroupref,
  					  List *sortgrouprefs, List *exprs);
  static List *addTargetToSortList(ParseState *pstate, TargetEntry *tle,
--- 71,78 ----
  static TargetEntry *findTargetlistEntrySQL92(ParseState *pstate, Node *node,
  						 List **tlist, ParseExprKind exprKind);
  static TargetEntry *findTargetlistEntrySQL99(ParseState *pstate, Node *node,
! 											 List **tlist, ParseExprKind exprKind,
! 											 bool keepDuplicates);
  static int get_matching_location(int sortgroupref,
  					  List *sortgrouprefs, List *exprs);
  static List *addTargetToSortList(ParseState *pstate, TargetEntry *tle,
***************
*** 1476,1482 **** findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
  	/*
  	 * Otherwise, we have an expression, so process it per SQL99 rules.
  	 */
! 	return findTargetlistEntrySQL99(pstate, node, tlist, exprKind);
  }
  
  /*
--- 1477,1483 ----
  	/*
  	 * Otherwise, we have an expression, so process it per SQL99 rules.
  	 */
! 	return findTargetlistEntrySQL99(pstate, node, tlist, exprKind, false);
  }
  
  /*
***************
*** 1491,1500 **** findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
   * node		the ORDER BY, GROUP BY, etc expression to be matched
   * tlist	the target list (passed by reference so we can append to it)
   * exprKind identifies clause type being processed
   */
  static TargetEntry *
  findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
! 						 ParseExprKind exprKind)
  {
  	TargetEntry *target_result;
  	ListCell   *tl;
--- 1492,1502 ----
   * node		the ORDER BY, GROUP BY, etc expression to be matched
   * tlist	the target list (passed by reference so we can append to it)
   * exprKind identifies clause type being processed
+  * keepDuplicates  if true, don't try and match to any existing entry
   */
  static TargetEntry *
  findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
! 						 ParseExprKind exprKind, bool keepDuplicates)
  {
  	TargetEntry *target_result;
  	ListCell   *tl;
***************
*** 1509,1532 **** findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
  	 */
  	expr = transformExpr(pstate, node, exprKind);
  
! 	foreach(tl, *tlist)
  	{
! 		TargetEntry *tle = (TargetEntry *) lfirst(tl);
! 		Node	   *texpr;
  
! 		/*
! 		 * Ignore any implicit cast on the existing tlist expression.
! 		 *
! 		 * This essentially allows the ORDER/GROUP/etc item to adopt the same
! 		 * datatype previously selected for a textually-equivalent tlist item.
! 		 * There can't be any implicit cast at top level in an ordinary SELECT
! 		 * tlist at this stage, but the case does arise with ORDER BY in an
! 		 * aggregate function.
! 		 */
! 		texpr = strip_implicit_coercions((Node *) tle->expr);
  
! 		if (equal(expr, texpr))
! 			return tle;
  	}
  
  	/*
--- 1511,1537 ----
  	 */
  	expr = transformExpr(pstate, node, exprKind);
  
! 	if (!keepDuplicates)
  	{
! 		foreach(tl, *tlist)
! 		{
! 			TargetEntry *tle = (TargetEntry *) lfirst(tl);
! 			Node	   *texpr;
  
! 			/*
! 			 * Ignore any implicit cast on the existing tlist expression.
! 			 *
! 			 * This essentially allows the ORDER/GROUP/etc item to adopt the same
! 			 * datatype previously selected for a textually-equivalent tlist item.
! 			 * There can't be any implicit cast at top level in an ordinary SELECT
! 			 * tlist at this stage, but the case does arise with ORDER BY in an
! 			 * aggregate function.
! 			 */
! 			texpr = strip_implicit_coercions((Node *) tle->expr);
  
! 			if (equal(expr, texpr))
! 				return tle;
! 		}
  	}
  
  	/*
***************
*** 1568,1574 **** transformGroupClause(ParseState *pstate, List *grouplist,
  
  		if (useSQL99)
  			tle = findTargetlistEntrySQL99(pstate, gexpr,
! 										   targetlist, exprKind);
  		else
  			tle = findTargetlistEntrySQL92(pstate, gexpr,
  										   targetlist, exprKind);
--- 1573,1579 ----
  
  		if (useSQL99)
  			tle = findTargetlistEntrySQL99(pstate, gexpr,
! 										   targetlist, exprKind, false);
  		else
  			tle = findTargetlistEntrySQL92(pstate, gexpr,
  										   targetlist, exprKind);
***************
*** 1635,1645 **** transformSortClause(ParseState *pstate,
  					List **targetlist,
  					ParseExprKind exprKind,
  					bool resolveUnknown,
! 					bool useSQL99)
  {
  	List	   *sortlist = NIL;
  	ListCell   *olitem;
  
  	foreach(olitem, orderlist)
  	{
  		SortBy	   *sortby = (SortBy *) lfirst(olitem);
--- 1640,1653 ----
  					List **targetlist,
  					ParseExprKind exprKind,
  					bool resolveUnknown,
! 					bool useSQL99,
! 	                bool keepDuplicates)
  {
  	List	   *sortlist = NIL;
  	ListCell   *olitem;
  
+ 	Assert(useSQL99 || !keepDuplicates);
+ 
  	foreach(olitem, orderlist)
  	{
  		SortBy	   *sortby = (SortBy *) lfirst(olitem);
***************
*** 1647,1653 **** transformSortClause(ParseState *pstate,
  
  		if (useSQL99)
  			tle = findTargetlistEntrySQL99(pstate, sortby->node,
! 										   targetlist, exprKind);
  		else
  			tle = findTargetlistEntrySQL92(pstate, sortby->node,
  										   targetlist, exprKind);
--- 1655,1661 ----
  
  		if (useSQL99)
  			tle = findTargetlistEntrySQL99(pstate, sortby->node,
! 										   targetlist, exprKind, keepDuplicates);
  		else
  			tle = findTargetlistEntrySQL92(pstate, sortby->node,
  										   targetlist, exprKind);
***************
*** 1717,1723 **** transformWindowDefinitions(ParseState *pstate,
  										  targetlist,
  										  EXPR_KIND_WINDOW_ORDER,
  										  true /* fix unknowns */ ,
! 										  true /* force SQL99 rules */ );
  		partitionClause = transformGroupClause(pstate,
  											   windef->partitionClause,
  											   targetlist,
--- 1725,1732 ----
  										  targetlist,
  										  EXPR_KIND_WINDOW_ORDER,
  										  true /* fix unknowns */ ,
! 										  true /* force SQL99 rules */,
! 			                              false /* don't add duplicates */);
  		partitionClause = transformGroupClause(pstate,
  											   windef->partitionClause,
  											   targetlist,
*** a/src/backend/parser/parse_collate.c
--- b/src/backend/parser/parse_collate.c
***************
*** 73,79 **** typedef struct
  static bool assign_query_collations_walker(Node *node, ParseState *pstate);
  static bool assign_collations_walker(Node *node,
  						 assign_collations_context *context);
! 
  
  /*
   * assign_query_collations()
--- 73,81 ----
  static bool assign_query_collations_walker(Node *node, ParseState *pstate);
  static bool assign_collations_walker(Node *node,
  						 assign_collations_context *context);
! static void assign_aggregate_collations(Aggref *aggref,
! 						assign_collations_context *context,
! 						assign_collations_context *loccontext);
  
  /*
   * assign_query_collations()
***************
*** 564,607 **** assign_collations_walker(Node *node, assign_collations_context *context)
  					case T_Aggref:
  						{
  							/*
! 							 * Aggref is a special case because expressions
! 							 * used only for ordering shouldn't be taken to
! 							 * conflict with each other or with regular args.
! 							 * So we apply assign_expr_collations() to them
! 							 * rather than passing down our loccontext.
! 							 *
! 							 * Note that we recurse to each TargetEntry, not
! 							 * directly to its contained expression, so that
! 							 * the case above for T_TargetEntry will apply
! 							 * appropriate checks to agg ORDER BY items.
! 							 *
! 							 * Likewise, we assign collations for the (bool)
! 							 * expression in aggfilter, independently of any
! 							 * other args.
! 							 *
! 							 * We need not recurse into the aggorder or
! 							 * aggdistinct lists, because those contain only
! 							 * SortGroupClause nodes which we need not
! 							 * process.
  							 */
  							Aggref	   *aggref = (Aggref *) node;
- 							ListCell   *lc;
  
! 							foreach(lc, aggref->args)
! 							{
! 								TargetEntry *tle = (TargetEntry *) lfirst(lc);
! 
! 								Assert(IsA(tle, TargetEntry));
! 								if (tle->resjunk)
! 									assign_expr_collations(context->pstate,
! 														   (Node *) tle);
! 								else
! 									(void) assign_collations_walker((Node *) tle,
! 																&loccontext);
! 							}
  
  							assign_expr_collations(context->pstate,
! 												 (Node *) aggref->aggfilter);
  						}
  						break;
  					case T_WindowFunc:
--- 566,581 ----
  					case T_Aggref:
  						{
  							/*
! 							 * Aggref is special enough that we give it its own
! 							 * function. The FILTER clause is independent of the
! 							 * rest of the aggregate, however.
  							 */
  							Aggref	   *aggref = (Aggref *) node;
  
! 							assign_aggregate_collations(aggref, context, &loccontext);
  
  							assign_expr_collations(context->pstate,
! 												   (Node *) aggref->aggfilter);
  						}
  						break;
  					case T_WindowFunc:
***************
*** 802,804 **** assign_collations_walker(Node *node, assign_collations_context *context)
--- 776,934 ----
  
  	return false;
  }
+ 
+ 
+ /*
+  * Aggref is a special case because expressions used only for ordering
+  * shouldn't be taken to conflict with each other or with regular args.  So we
+  * apply assign_expr_collations() to them rather than passing down our
+  * loccontext.
+  *
+  * Note that we recurse to each TargetEntry, not directly to its contained
+  * expression, so that the case above for T_TargetEntry will apply appropriate
+  * checks to agg ORDER BY items.
+  *
+  * We need not recurse into the aggorder or aggdistinct lists, because those
+  * contain only SortGroupClause nodes which we need not process.
+  *
+  * For ordered set functions, it's unfortunately unclear how best to proceed.
+  * The spec-defined inverse distribution functions have only one sort column
+  * and don't allow collatable types, but this is clearly unsatisfactory in the
+  * general case. Compromise by taking the sort column as part of the collation
+  * determination if, and only if, there is only one such column, and force the
+  * final choice of input collation down into the sort column if need be; but
+  * don't error out unless actually necessary (leaving it up to the function to
+  * handle the issue at runtime). This ugly wart is justified by the fact that
+  * there seems to be no other good way to get a result collation for
+  * percentile_* applied to a collatable type.
+  *
+  * But hypothetical set functions are special; they must have
+  * pairwise-assigned collations for each matching pair of args, and again we
+  * need to force the final choice of collation down into the sort column to
+  * ensure that the sort happens on the chosen collation. If there are any
+  * additional args (not allowed in the spec, but a user-defined function might
+  * have some), those contribute to the result collation in the normal way.
+  * (The hypothetical paired args never contribute to the result collation at
+  * all.)
+  */
+ 
+ static Expr *
+ relabel_expr_collation(Expr *expr, Oid newcollation)
+ {
+ 	RelabelType *node = makeNode(RelabelType);
+ 	node->arg = expr;
+ 	node->resulttype = exprType((Node *)expr);
+ 	node->resulttypmod = exprTypmod((Node *)expr);
+ 	node->resultcollid = newcollation;
+ 	node->relabelformat = COERCE_IMPLICIT_CAST;
+ 	node->location = exprLocation((Node *)expr);
+ 	return (Expr *) node;
+ }
+ 
+ static void
+ assign_aggregate_collations(Aggref *aggref,
+ 							assign_collations_context *context,
+ 							assign_collations_context *loccontext)
+ {
+ 	ListCell   *lc;
+ 
+ 	if (aggref->ishypothetical)
+ 	{
+ 		/*-
+ 		 * Hypothetical set function, i.e.
+ 		 *   func(..., a,b,c,...) within group (p,q,r,...)
+ 		 *
+ 		 * Any initial set of direct args (before "a") contributes to the
+ 		 * result collation in the usual way for function args. But none of
+ 		 * a,b,c... or p,q,r... contribute at all; instead, they must be
+ 		 * paired up (as though UNIONed) and the sorted col's collation forced
+ 		 * to the chosen value (so that we sort it correctly).
+ 		 */
+ 		int initial_args = list_length(aggref->orddirectargs) - list_length(aggref->args);
+ 		ListCell *h_arg = list_head(aggref->orddirectargs);
+ 		ListCell *s_arg = list_head(aggref->args);
+ 
+ 		Assert(initial_args >= 0);
+ 
+ 		while (initial_args-- > 0)
+ 		{
+ 			(void) assign_collations_walker((Node *) lfirst(h_arg), loccontext);
+ 			h_arg = lnext(h_arg);
+ 		}
+ 
+ 		for_each_cell(h_arg,h_arg)
+ 		{
+ 			TargetEntry *tle = (TargetEntry *) lfirst(s_arg);
+ 			Oid coll = select_common_collation(context->pstate,
+ 											   list_make2(lfirst(h_arg),lfirst(s_arg)),
+ 											   false);
+ 
+ 			/*
+ 			 * we can only get InvalidOid here if the type is not collatable,
+ 			 * so no need to try and relabel in that case.
+ 			 */
+ 
+ 			if (OidIsValid(coll)
+ 				&& coll != exprCollation((Node *)(tle->expr)))
+ 			{
+ 				tle->expr = relabel_expr_collation(tle->expr, coll);
+ 			}
+ 
+ 			s_arg = lnext(s_arg);
+ 		}
+ 	}
+ 	else if (aggref->isordset && list_length(aggref->args) == 1)
+ 	{
+ 		/*
+ 		 * Ordered set func with one sorted arg
+ 		 */
+ 		TargetEntry *tle = (TargetEntry *) linitial(aggref->args);
+ 
+ 		/* do the TLE first so that it won't error out on conflicts */
+ 
+ 		(void) assign_collations_walker((Node *) tle,
+ 										loccontext);
+ 
+ 		(void) assign_collations_walker((Node *) aggref->orddirectargs,
+ 										loccontext);
+ 
+ 		/*
+ 		 * If the sort col is a collatable type, and we chose a collation,
+ 		 * and it's not the one the sort col already has, then force the
+ 		 * sort col's collation (which can't have been explicit) to the
+ 		 * chosen one. Otherwise leave it alone.
+ 		 */
+ 		if (type_is_collatable(exprType((Node *)(tle->expr)))
+ 			&& (loccontext->strength == COLLATE_IMPLICIT
+ 				|| loccontext->strength == COLLATE_EXPLICIT)
+ 			&& exprCollation((Node *)(tle->expr)) != loccontext->collation)
+ 		{
+ 			tle->expr = relabel_expr_collation(tle->expr, loccontext->collation);
+ 		}
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * For this case, we do the direct args (if any) together, as is
+ 		 * normal for functions, but args which are either used only for
+ 		 * sorting or are only part of a WITHIN GROUP are processed
+ 		 * individually.
+ 		 */
+ 
+ 		(void) assign_collations_walker((Node *) aggref->orddirectargs,
+ 										loccontext);
+ 
+ 		foreach(lc, aggref->args)
+ 		{
+ 			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ 
+ 			Assert(IsA(tle, TargetEntry));
+ 			if (tle->resjunk)
+ 				assign_expr_collations(context->pstate,
+ 									   (Node *) tle);
+ 			else
+ 				(void) assign_collations_walker((Node *) tle,
+ 												loccontext);
+ 		}
+ 	}
+ }
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
***************
*** 463,470 **** transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
  			newresult = ParseFuncOrColumn(pstate,
  										  list_make1(n),
  										  list_make1(result),
! 										  NIL, NULL, false, false, false,
! 										  NULL, true, location);
  			if (newresult == NULL)
  				unknown_attribute(pstate, result, strVal(n), location);
  			result = newresult;
--- 463,470 ----
  			newresult = ParseFuncOrColumn(pstate,
  										  list_make1(n),
  										  list_make1(result),
! 										  location,
! 										  NULL);
  			if (newresult == NULL)
  				unknown_attribute(pstate, result, strVal(n), location);
  			result = newresult;
***************
*** 631,638 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
  					node = ParseFuncOrColumn(pstate,
  											 list_make1(makeString(colname)),
  											 list_make1(node),
! 											 NIL, NULL, false, false, false,
! 											 NULL, true, cref->location);
  				}
  				break;
  			}
--- 631,637 ----
  					node = ParseFuncOrColumn(pstate,
  											 list_make1(makeString(colname)),
  											 list_make1(node),
! 											 cref->location, NULL);
  				}
  				break;
  			}
***************
*** 676,683 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
  					node = ParseFuncOrColumn(pstate,
  											 list_make1(makeString(colname)),
  											 list_make1(node),
! 											 NIL, NULL, false, false, false,
! 											 NULL, true, cref->location);
  				}
  				break;
  			}
--- 675,681 ----
  					node = ParseFuncOrColumn(pstate,
  											 list_make1(makeString(colname)),
  											 list_make1(node),
! 											 cref->location, NULL);
  				}
  				break;
  			}
***************
*** 734,741 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
  					node = ParseFuncOrColumn(pstate,
  											 list_make1(makeString(colname)),
  											 list_make1(node),
! 											 NIL, NULL, false, false, false,
! 											 NULL, true, cref->location);
  				}
  				break;
  			}
--- 732,738 ----
  					node = ParseFuncOrColumn(pstate,
  											 list_make1(makeString(colname)),
  											 list_make1(node),
! 											 cref->location, NULL);
  				}
  				break;
  			}
***************
*** 1242,1279 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
  {
  	List	   *targs;
  	ListCell   *args;
- 	Expr	   *tagg_filter;
  
  	/* Transform the list of arguments ... */
  	targs = NIL;
  	foreach(args, fn->args)
  	{
! 		targs = lappend(targs, transformExprRecurse(pstate,
! 													(Node *) lfirst(args)));
  	}
  
- 	/*
- 	 * Transform the aggregate filter using transformWhereClause(), to which
- 	 * FILTER is virtually identical...
- 	 */
- 	tagg_filter = NULL;
- 	if (fn->agg_filter != NULL)
- 		tagg_filter = (Expr *)
- 			transformWhereClause(pstate, (Node *) fn->agg_filter,
- 								 EXPR_KIND_FILTER, "FILTER");
- 
  	/* ... and hand off to ParseFuncOrColumn */
  	return ParseFuncOrColumn(pstate,
  							 fn->funcname,
  							 targs,
! 							 fn->agg_order,
! 							 tagg_filter,
! 							 fn->agg_star,
! 							 fn->agg_distinct,
! 							 fn->func_variadic,
! 							 fn->over,
! 							 false,
! 							 fn->location);
  }
  
  static Node *
--- 1239,1258 ----
  {
  	List	   *targs;
  	ListCell   *args;
  
  	/* Transform the list of arguments ... */
  	targs = NIL;
  	foreach(args, fn->args)
  	{
! 		targs = lappend(targs, transformExprRecurse(pstate, (Node *) lfirst(args)));
  	}
  
  	/* ... and hand off to ParseFuncOrColumn */
  	return ParseFuncOrColumn(pstate,
  							 fn->funcname,
  							 targs,
! 							 fn->location,
! 							 fn);
  }
  
  static Node *
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
***************
*** 17,32 ****
--- 17,35 ----
  #include "access/htup_details.h"
  #include "catalog/pg_proc.h"
  #include "catalog/pg_type.h"
+ #include "catalog/pg_aggregate.h"
  #include "funcapi.h"
  #include "lib/stringinfo.h"
  #include "nodes/makefuncs.h"
  #include "nodes/nodeFuncs.h"
  #include "parser/parse_agg.h"
+ #include "parser/parse_clause.h"
  #include "parser/parse_coerce.h"
  #include "parser/parse_func.h"
  #include "parser/parse_relation.h"
  #include "parser/parse_target.h"
  #include "parser/parse_type.h"
+ #include "parser/parse_expr.h"
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
  #include "utils/syscache.h"
***************
*** 56,70 **** static Node *ParseComplexProjection(ParseState *pstate, char *funcname,
   *	Also, when is_column is true, we return NULL on failure rather than
   *	reporting a no-such-function error.
   *
!  *	The argument expressions (in fargs) and filter must have been transformed
!  *	already.  But the agg_order expressions, if any, have not been.
   */
  Node *
  ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
! 				  List *agg_order, Expr *agg_filter,
! 				  bool agg_star, bool agg_distinct, bool func_variadic,
! 				  WindowDef *over, bool is_column, int location)
  {
  	Oid			rettype;
  	Oid			funcid;
  	ListCell   *l;
--- 59,79 ----
   *	Also, when is_column is true, we return NULL on failure rather than
   *	reporting a no-such-function error.
   *
!  *	The argument expressions (in fargs) must have been transformed
!  *	already.
   */
  Node *
  ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
! 				  int location, FuncCall *fn)
  {
+ 	List       *agg_order = (fn ? fn->agg_order : NIL);
+ 	Expr       *agg_filter = NULL;
+ 	bool        agg_star = (fn ? fn->agg_star : false);
+ 	bool        agg_distinct = (fn ? fn->agg_distinct : false);
+ 	bool        agg_within_group = (fn ? fn->has_within_group : false);
+ 	bool        func_variadic = (fn ? fn->func_variadic : false);
+ 	WindowDef  *over = (fn ? fn->over : NULL);
+ 	bool        is_column = (fn == NULL);
  	Oid			rettype;
  	Oid			funcid;
  	ListCell   *l;
***************
*** 81,86 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 90,101 ----
  	int			nvargs;
  	Oid			vatype;
  	FuncDetailCode fdresult;
+ 	int		number_of_args = -1;
+ 	bool		isordsetfunc = false;
+ 	bool		ishypotheticalsetfunc = false;
+ 
+ 	/* Check if the function has WITHIN GROUP as well as distinct. */
+ 	Assert(!(agg_within_group && agg_distinct));
  
  	/*
  	 * Most of the rest of the parser just assumes that functions do not have
***************
*** 98,103 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 113,127 ----
  				 parser_errposition(pstate, location)));
  
  	/*
+ 	 * Transform the aggregate filter using transformWhereClause(), to which
+ 	 * FILTER is virtually identical...
+ 	 */
+ 	if (fn && fn->agg_filter != NULL)
+ 		agg_filter = (Expr *)
+ 			transformWhereClause(pstate, (Node *) fn->agg_filter,
+ 								 EXPR_KIND_FILTER, "FILTER");
+ 
+ 	/*
  	 * Extract arg type info in preparation for function lookup.
  	 *
  	 * If any arguments are Param markers of type VOID, we discard them from
***************
*** 163,168 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 187,198 ----
  		}
  	}
  
+ 	if (agg_within_group && argnames)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("ordered set functions cannot use named arguments"),
+ 				 parser_errposition(pstate, location)));
+ 
  	if (fargs)
  	{
  		first_arg = linitial(fargs);
***************
*** 170,175 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 200,225 ----
  	}
  
  	/*
+ 	 * If WITHIN GROUP is present, we need to call transformExpr on each
+ 	 * SortBy node in agg_order, then call exprType and append to
+ 	 * actual_arg_types, in order to get the types of values in WITHIN GROUP
+ 	 * clause.
+ 	 */
+ 	if (agg_within_group)
+ 	{
+ 		Assert(agg_order != NIL);
+ 
+ 		foreach(l, agg_order)
+ 		{
+ 			SortBy	   *arg = (SortBy *) lfirst(l);
+ 
+ 			arg->node = transformExpr(pstate, arg->node, EXPR_KIND_ORDER_BY);
+ 
+ 			actual_arg_types[nargs++] = exprType(arg->node);
+ 		}
+ 	}
+ 
+ 	/*
  	 * Check for column projection: if function has one argument, and that
  	 * argument is of complex type, and function name is not qualified, then
  	 * the "function call" could be a projection.  We also check that there
***************
*** 247,252 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 297,308 ----
  			errmsg("DISTINCT specified, but %s is not an aggregate function",
  				   NameListToString(funcname)),
  					 parser_errposition(pstate, location)));
+ 		if (agg_within_group)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 			errmsg("WITHIN GROUP specified, but %s is not an ordered set function",
+ 				   NameListToString(funcname)),
+ 					 parser_errposition(pstate, location)));
  		if (agg_order != NIL)
  			ereport(ERROR,
  					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 266,271 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 322,373 ----
  							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("Incorrect number of arguments for hypothetical set function"),
+ 							 parser_errposition(pstate, location)));
+ 			}
+ 			else
+ 			{
+ 				number_of_args = classForm->aggordnargs;
+ 			}
+ 
+ 			if (!agg_within_group)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 						 errmsg("WITHIN GROUP is required for call to ordered set function %s",
+ 								NameListToString(funcname)),
+ 						 parser_errposition(pstate, location)));
+ 		
+ 			if (over)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("OVER clause not supported for call to ordered set function %s",
+ 								NameListToString(funcname)),
+ 						 parser_errposition(pstate, location)));
+ 		}
+ 
+ 		ReleaseSysCache(tup);
+ 	}
  	else if (!(fdresult == FUNCDETAIL_AGGREGATE ||
  			   fdresult == FUNCDETAIL_WINDOWFUNC))
  	{
***************
*** 351,363 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
  											   false);
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, fargs, actual_arg_types, declared_arg_types);
  
  	/*
  	 * If it's a variadic function call, transform the last nvargs arguments
  	 * into an array --- unless it's an "any" variadic.
  	 */
! 	if (nvargs > 0 && declared_arg_types[nargs - 1] != ANYOID)
  	{
  		ArrayExpr  *newa = makeNode(ArrayExpr);
  		int			non_var_args = nargs - nvargs;
--- 453,468 ----
  											   false);
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, fargs, (isordsetfunc) ? agg_order : NIL, 
! 					  actual_arg_types, 
! 					  declared_arg_types,
! 					  ishypotheticalsetfunc);
  
  	/*
  	 * If it's a variadic function call, transform the last nvargs arguments
  	 * into an array --- unless it's an "any" variadic.
  	 */
! 	if (nvargs > 0 && vatype != ANYOID)
  	{
  		ArrayExpr  *newa = makeNode(ArrayExpr);
  		int			non_var_args = nargs - nvargs;
***************
*** 388,403 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
  	 * When function is called with an explicit VARIADIC labeled parameter,
  	 * and the declared_arg_type is "any", then sanity check the actual
  	 * parameter type now - it must be an array.
  	 */
  	if (nargs > 0 && vatype == ANYOID && func_variadic)
  	{
! 		Oid		va_arr_typid = actual_arg_types[nargs - 1];
  
  		if (!OidIsValid(get_element_type(va_arr_typid)))
  			ereport(ERROR,
  					(errcode(ERRCODE_DATATYPE_MISMATCH),
  					 errmsg("VARIADIC argument must be an array"),
! 			  parser_errposition(pstate, exprLocation((Node *) llast(fargs)))));
  	}
  
  	/* build the appropriate output structure */
--- 493,523 ----
  	 * When function is called with an explicit VARIADIC labeled parameter,
  	 * and the declared_arg_type is "any", then sanity check the actual
  	 * parameter type now - it must be an array.
+ 	 *
+ 	 * Also, it can't be a hypothetical set function, and if it's an ordered
+ 	 * set function, the variadic labeled parameter is the last _direct_ arg,
+ 	 * not an ordered arg. (In practice we're unlikely to get this far for
+ 	 * hypotheticals, since make_fn_arguments would probably fail to unify
+ 	 * types, but we can't change the order of these.)
  	 */
  	if (nargs > 0 && vatype == ANYOID && func_variadic)
  	{
! 		int     ignore_args = (agg_within_group ? list_length(agg_order) : 0);
! 		Oid		va_arr_typid = actual_arg_types[nargs - 1 - ignore_args];
! 
! 		if (ishypotheticalsetfunc)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_SYNTAX_ERROR),
! 			  errmsg("explicit VARIADIC argument not allowed for hypothetical set function"),
! 			  parser_errposition(pstate,
! 								 exprLocation((Node *) list_nth(fargs, nargs - 1 - ignore_args)))));
  
  		if (!OidIsValid(get_element_type(va_arr_typid)))
  			ereport(ERROR,
  					(errcode(ERRCODE_DATATYPE_MISMATCH),
  					 errmsg("VARIADIC argument must be an array"),
! 			  parser_errposition(pstate,
! 								 exprLocation((Node *) list_nth(fargs, nargs - 1 - ignore_args)))));
  	}
  
  	/* build the appropriate output structure */
***************
*** 421,426 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 541,552 ----
  		/* aggregate function */
  		Aggref	   *aggref = makeNode(Aggref);
  
+ 		if (agg_within_group && !isordsetfunc)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 					 errmsg("%s is not an ordered set function",
+ 							func_signature_string(funcname, nargs, NIL, actual_arg_types))));
+ 
  		aggref->aggfnoid = funcid;
  		aggref->aggtype = rettype;
  		/* aggcollid and inputcollid will be set by parse_collate.c */
***************
*** 428,441 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
  		aggref->aggfilter = agg_filter;
  		aggref->aggstar = agg_star;
  		aggref->aggvariadic = func_variadic;
  		/* agglevelsup will be set by transformAggregateCall */
  		aggref->location = location;
  
  		/*
  		 * Reject attempt to call a parameterless aggregate without (*)
  		 * syntax.	This is mere pedantry but some folks insisted ...
  		 */
! 		if (fargs == NIL && !agg_star)
  			ereport(ERROR,
  					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
  					 errmsg("%s(*) must be used to call a parameterless aggregate function",
--- 554,577 ----
  		aggref->aggfilter = agg_filter;
  		aggref->aggstar = agg_star;
  		aggref->aggvariadic = func_variadic;
+ 		aggref->ishypothetical = ishypotheticalsetfunc;
  		/* agglevelsup will be set by transformAggregateCall */
  		aggref->location = location;
  
+ 		if (isordsetfunc
+ 			&& number_of_args >= 0
+ 			&& number_of_args != list_length(fargs))
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 					 errmsg("Incorrect number of direct arguments to ordered set function %s",
+ 							NameListToString(funcname)),
+ 					 parser_errposition(pstate, location)));
+ 
  		/*
  		 * Reject attempt to call a parameterless aggregate without (*)
  		 * syntax.	This is mere pedantry but some folks insisted ...
  		 */
! 		if (fargs == NIL && !agg_star && !agg_within_group)
  			ereport(ERROR,
  					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
  					 errmsg("%s(*) must be used to call a parameterless aggregate function",
***************
*** 464,470 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
  					 parser_errposition(pstate, location)));
  
  		/* parse_agg.c does additional aggregate-specific processing */
! 		transformAggregateCall(pstate, aggref, fargs, agg_order, agg_distinct);
  
  		retval = (Node *) aggref;
  	}
--- 600,607 ----
  					 parser_errposition(pstate, location)));
  
  		/* parse_agg.c does additional aggregate-specific processing */
! 		transformAggregateCall(pstate, aggref, fargs, agg_order,
! 							   agg_distinct, agg_within_group);
  
  		retval = (Node *) aggref;
  	}
***************
*** 473,478 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 610,621 ----
  		/* 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,1384 **** func_get_detail(List *funcname,
  void
  make_fn_arguments(ParseState *pstate,
  				  List *fargs,
  				  Oid *actual_arg_types,
! 				  Oid *declared_arg_types)
  {
  	ListCell   *current_fargs;
  	int			i = 0;
  
  	foreach(current_fargs, fargs)
  	{
--- 1517,1537 ----
  void
  make_fn_arguments(ParseState *pstate,
  				  List *fargs,
+ 				  List *agg_order,
  				  Oid *actual_arg_types,
! 				  Oid *declared_arg_types,
! 				  bool requiresUnification)
  {
  	ListCell   *current_fargs;
+ 	ListCell   *current_aoargs;
  	int			i = 0;
+ 	int         unify_offset = -1;
+ 
+ 	if (requiresUnification)
+ 	{
+ 		unify_offset = list_length(fargs) - list_length(agg_order);
+ 		Assert(unify_offset >= 0);
+ 	}
  
  	foreach(current_fargs, fargs)
  	{
***************
*** 1386,1391 **** make_fn_arguments(ParseState *pstate,
--- 1539,1545 ----
  		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,1423 **** make_fn_arguments(ParseState *pstate,
  			}
  			else
  			{
! 				node = coerce_type(pstate,
! 								   node,
! 								   actual_arg_types[i],
! 								   declared_arg_types[i], -1,
! 								   COERCION_IMPLICIT,
! 								   COERCE_IMPLICIT_CAST,
! 								   -1);
! 				lfirst(current_fargs) = node;
  			}
  		}
  		i++;
  	}
  }
  
  /*
--- 1560,1625 ----
  			}
  			else
  			{
! 				/* 
! 				 * If we are dealing with a hypothetical set function, we
! 				 * need to unify agg_order and fargs.
! 				 */
! 
! 				if (declared_arg_types[i] == ANYOID && requiresUnification)
! 				{
! 					Oid unification_oid;
! 					SortBy *unify_with = (SortBy *) list_nth(agg_order,i - unify_offset);
! 
! 					unification_oid = select_common_type(pstate,
! 														 list_make2(unify_with->node,node),
! 														 "WITHIN GROUP",
! 														 NULL);
! 
! 					declared_arg_types[i + list_length(agg_order)] = unification_oid;
! 
! 					temp = coerce_type(pstate,
! 									   node,
! 									   actual_arg_types[i],
! 									   unification_oid, -1,
! 									   COERCION_IMPLICIT,
! 									   COERCE_IMPLICIT_CAST,
! 									   -1);
! 				}
! 				else
! 				{
! 					temp = coerce_type(pstate,
! 									   node,
! 									   actual_arg_types[i],
! 									   declared_arg_types[i], -1,
! 									   COERCION_IMPLICIT,
! 									   COERCE_IMPLICIT_CAST,
! 									   -1);
! 				}
! 
! 				lfirst(current_fargs) = temp;
  			}
  		}
  		i++;
  	}
+ 
+ 	foreach(current_aoargs, agg_order)
+ 	{
+ 		if (actual_arg_types[i] != declared_arg_types[i])
+ 		{
+ 			SortBy	   *node = (SortBy *) lfirst(current_aoargs);
+ 			Node       *temp = NULL;
+ 
+ 			temp = coerce_type(pstate,
+ 							   node->node,
+ 							   actual_arg_types[i],
+ 							   declared_arg_types[i], -1,
+ 							   COERCION_IMPLICIT,
+ 							   COERCE_IMPLICIT_CAST,
+ 							   -1);
+ 			node->node = temp;
+ 		}
+ 		i++;
+ 	}
  }
  
  /*
*** a/src/backend/parser/parse_oper.c
--- b/src/backend/parser/parse_oper.c
***************
*** 823,829 **** make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
  											   false);
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
  
  	/* and build the expression node */
  	result = makeNode(OpExpr);
--- 823,829 ----
  											   false);
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types, false);
  
  	/* and build the expression node */
  	result = makeNode(OpExpr);
***************
*** 953,959 **** make_scalar_array_op(ParseState *pstate, List *opname,
  	declared_arg_types[1] = res_atypeId;
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
  
  	/* and build the expression node */
  	result = makeNode(ScalarArrayOpExpr);
--- 953,959 ----
  	declared_arg_types[1] = res_atypeId;
  
  	/* perform the necessary typecasting of arguments */
! 	make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types, false);
  
  	/* and build the expression node */
  	result = makeNode(ScalarArrayOpExpr);
*** a/src/backend/utils/adt/Makefile
--- b/src/backend/utils/adt/Makefile
***************
*** 19,31 **** OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
  	array_userfuncs.o arrayutils.o bool.o \
  	cash.o char.o date.o datetime.o datum.o domains.o \
  	enum.o float.o format_type.o \
! 	geo_ops.o geo_selfuncs.o int.o int8.o json.o jsonfuncs.o like.o \
  	lockfuncs.o misc.o nabstime.o name.o numeric.o numutils.o \
  	oid.o oracle_compat.o pseudotypes.o rangetypes.o rangetypes_gist.o \
  	rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \
  	tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \
  	network.o mac.o inet_cidr_ntop.o inet_net_pton.o \
! 	ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \
  	ascii.o quote.o pgstatfuncs.o encode.o dbsize.o genfile.o trigfuncs.o \
  	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
  	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
--- 19,31 ----
  	array_userfuncs.o arrayutils.o bool.o \
  	cash.o char.o date.o datetime.o datum.o domains.o \
  	enum.o float.o format_type.o \
! 	geo_ops.o geo_selfuncs.o hypotheticalset.o int.o int8.o json.o jsonfuncs.o like.o \
  	lockfuncs.o misc.o nabstime.o name.o numeric.o numutils.o \
  	oid.o oracle_compat.o pseudotypes.o rangetypes.o rangetypes_gist.o \
  	rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \
  	tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \
  	network.o mac.o inet_cidr_ntop.o inet_net_pton.o \
! 	inversedistribution.o ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \
  	ascii.o quote.o pgstatfuncs.o encode.o dbsize.o genfile.o trigfuncs.o \
  	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
  	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
*** /dev/null
--- b/src/backend/utils/adt/hypotheticalset.c
***************
*** 0 ****
--- 1,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);
+ }
*** /dev/null
--- b/src/backend/utils/adt/inversedistribution.c
***************
*** 0 ****
--- 1,662 ----
+ /*-------------------------------------------------------------------------
+  *
+  * inversedistribution.c
+  *	  Inverse distribution functions.
+  *
+  * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  *
+  * IDENTIFICATION
+  *	  src/backend/utils/adt/inversedistribution.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ #include "postgres.h"
+ #include "fmgr.h"
+ #include <string.h>
+ #include <math.h>
+ 
+ #include "utils/tuplesort.h"
+ #include "catalog/pg_type.h"
+ #include "utils/datetime.h"
+ #include "utils/lsyscache.h"
+ #include "utils/array.h"
+ 
+ /*
+  * percentile_disc(float8)  - discrete percentile
+  */
+ 
+ Datum percentile_disc_final(PG_FUNCTION_ARGS);
+ 
+ Datum
+ percentile_disc_final(PG_FUNCTION_ARGS)
+ {
+ 	float8		percentile;
+ 	Tuplesortstate *sorter;
+ 	Oid			datumtype;
+ 	Datum		val;
+ 	bool		isnull;
+ 	int64		skiprows;
+ 	int64		rowcount   = AggSetGetRowCount(fcinfo);
+ 
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	percentile = PG_GETARG_FLOAT8(0);
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ 
+ 	if (percentile < 0 || percentile > 1 || isnan(percentile))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("percentile value %g must be between 0 and 1", percentile)));
+ 
+ 	if (rowcount < 1)
+ 		PG_RETURN_NULL();
+ 
+ 	tuplesort_performsort(sorter);
+ 
+ 	/*
+ 	 * We need the smallest K such that (K/N) >= percentile. K starts at 1.
+ 	 * Therefore K >= N*percentile
+ 	 * Therefore K = ceil(N*percentile)
+ 	 * So we skip K-1 rows (if K>0) and return the next row fetched.
+ 	 *
+ 	 * We don't actually expect to see nulls in the input, our strict flag
+ 	 * should have filtered them out, but we're required to not crash if
+ 	 * there is one.
+ 	 */
+ 
+ 	skiprows = (int64) ceil(percentile * rowcount);
+ 	Assert(skiprows <= rowcount);
+ 
+ 	while (--skiprows > 0)
+ 		if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ 			elog(ERROR,"missing row in percentile_disc");
+ 
+ 	if (!tuplesort_getdatum(sorter, true, &val, &isnull))
+ 		elog(ERROR,"missing row in percentile_disc");
+ 
+ 	if (isnull)
+ 		PG_RETURN_NULL();
+ 	else
+ 		PG_RETURN_DATUM(val);
+ }
+ 
+ 
+ /*
+  * For percentile_cont, we need a way to interpolate between consecutive
+  * values. Use a helper function for that, so that we can share the rest
+  * of the code between types.
+  */
+ 
+ static Datum float8_lerp(Datum lo, Datum hi, float8 pct)
+ {
+ 	float8 loval = DatumGetFloat8(lo);
+ 	float8 hival = DatumGetFloat8(hi); 
+ 	return Float8GetDatum(loval + (pct * (hival - loval)));
+ }
+ 
+ static Datum interval_lerp(Datum lo, Datum hi, float8 pct)
+ {
+ 	Datum diff_result = DirectFunctionCall2(interval_mi, hi, lo);
+ 	Datum mul_result  = DirectFunctionCall2(interval_mul,
+ 											diff_result,
+ 											Float8GetDatumFast(pct));
+ 	return DirectFunctionCall2(interval_pl, mul_result, lo);
+ }
+ 
+ typedef Datum (*LerpFunc)(Datum lo, Datum hi, float8 pct);
+ 
+ static Datum
+ percentile_cont_final_common(FunctionCallInfo fcinfo,
+ 							 Oid expect_type,
+ 							 LerpFunc lerpfunc)
+ {
+ 	float8		percentile;
+ 	int64		rowcount = AggSetGetRowCount(fcinfo);
+ 	Tuplesortstate *sorter;
+ 	Oid			datumtype;
+ 	Datum		val;
+ 	Datum		first_row;
+ 	Datum		second_row;
+ 	float8		proportion;
+ 	bool		isnull;
+ 	int64		skiprows;
+ 	int64		lower_row = 0;
+ 	int64		higher_row = 0;
+ 
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	percentile = PG_GETARG_FLOAT8(0);
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ 
+ 	Assert(datumtype == expect_type);
+ 
+ 	if (percentile < 0 || percentile > 1 || isnan(percentile))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("percentile value %g must be between 0 and 1", percentile)));
+ 
+ 	if (rowcount < 1)
+ 		PG_RETURN_NULL();
+ 
+ 	tuplesort_performsort(sorter);
+ 
+ 	lower_row = floor(percentile * (rowcount - 1));
+ 	higher_row = ceil(percentile * (rowcount - 1));
+ 
+ 	Assert(lower_row < rowcount);
+ 
+ 	for (skiprows = lower_row; skiprows > 0; --skiprows)
+ 		if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ 			elog(ERROR,"missing row in percentile_cont");
+ 
+ 	if (!tuplesort_getdatum(sorter, true, &first_row, &isnull))
+ 		elog(ERROR,"missing row in percentile_cont");
+ 	if (isnull)
+ 		PG_RETURN_NULL();
+ 
+ 	if (lower_row == higher_row)
+ 	{
+ 		val = first_row;
+ 	}
+ 	else
+ 	{
+ 		if (!tuplesort_getdatum(sorter, true, &second_row, &isnull))
+ 			elog(ERROR,"missing row in percentile_cont");
+ 
+ 		if (isnull)
+ 			PG_RETURN_NULL();
+ 
+ 		proportion = (percentile * (rowcount-1)) - lower_row;
+ 		val = lerpfunc(first_row, second_row, proportion);
+ 	}
+ 
+ 	if (isnull)
+ 		PG_RETURN_NULL();
+ 	else
+ 		PG_RETURN_DATUM(val);
+ }
+ 
+ 
+ 
+ /*
+  * percentile_cont(float8)  - continuous percentile
+  */
+ 
+ Datum percentile_cont_float8_final(PG_FUNCTION_ARGS);
+ Datum percentile_cont_interval_final(PG_FUNCTION_ARGS);
+ 
+ Datum
+ percentile_cont_float8_final(PG_FUNCTION_ARGS)
+ {
+ 	return percentile_cont_final_common(fcinfo, FLOAT8OID, float8_lerp);
+ }
+ 
+ /*
+  * percentile_interval_cont(Interval)  - continuous percentile for Interval
+  */
+ 
+ Datum
+ percentile_cont_interval_final(PG_FUNCTION_ARGS)
+ {
+ 	return percentile_cont_final_common(fcinfo, INTERVALOID, interval_lerp);
+ }
+ 
+ 
+ /*
+  * mode() - most common value
+  */
+ 
+ Datum mode_final(PG_FUNCTION_ARGS);
+ 
+ Datum
+ mode_final(PG_FUNCTION_ARGS)
+ {
+ 	Tuplesortstate *sorter;
+ 	Oid			datumtype;
+ 	bool		isnull;
+ 	Datum		val;
+ 	Datum		last_val = (Datum) 0;
+ 	bool		last_val_is_mode = false;
+ 	int64		val_freq = 0;
+ 	Datum		mode_val = (Datum) 0;
+ 	int64		mode_freq = 0;
+ 	FmgrInfo   *equalfn;
+ 	bool		shouldfree;
+ 
+ 	struct mode_type_info {
+ 		Oid typid;
+ 		int16 typLen;
+ 		bool typByVal;
+ 	} *typinfo = fcinfo->flinfo->fn_extra;
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ 	AggSetGetDistinctInfo(fcinfo, NULL, NULL, &equalfn);
+ 
+ 	if (!typinfo || typinfo->typid != datumtype)
+ 	{
+ 		if (typinfo)
+ 			pfree(typinfo);
+ 		typinfo = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 									 sizeof(struct mode_type_info));
+ 		typinfo->typid = datumtype;
+ 		get_typlenbyval(datumtype, &typinfo->typLen, &typinfo->typByVal);
+ 	}
+ 
+ 	shouldfree = !(typinfo->typByVal);
+ 
+ 	tuplesort_performsort(sorter);
+ 
+ 	while (tuplesort_getdatum(sorter, true, &val, &isnull))
+ 	{
+ 		if (isnull)
+ 			continue;
+ 
+ 		if (val_freq == 0)
+ 		{
+ 			/* first value - assume modal until shown otherwise */
+ 			mode_val = last_val = val;
+ 			mode_freq = val_freq = 1;
+ 			last_val_is_mode = true;
+ 		}
+ 		else if (DatumGetBool(FunctionCall2(equalfn, val, last_val)))
+ 		{
+ 			/* value equal to previous value */
+ 			if (last_val_is_mode)
+ 				++mode_freq;
+ 			else if (++val_freq > mode_freq)
+ 			{
+ 				if (shouldfree)
+ 				{
+ 					pfree(DatumGetPointer(mode_val));
+ 					pfree(DatumGetPointer(val));
+ 				}
+ 
+ 				mode_val = last_val;
+ 				mode_freq = val_freq;
+ 				last_val_is_mode = true;
+ 			}
+ 			else if (shouldfree)
+ 				pfree(DatumGetPointer(val));
+ 		}
+ 		else
+ 		{
+ 			if (shouldfree && !last_val_is_mode)
+ 				pfree(DatumGetPointer(last_val));
+ 
+ 			last_val_is_mode = false;
+ 			last_val = val;
+ 			val_freq = 1;
+ 		}
+ 	}
+ 
+ 	if (shouldfree && !last_val_is_mode)
+ 		pfree(DatumGetPointer(last_val));
+ 
+ 	if (mode_freq)
+ 		PG_RETURN_DATUM(mode_val);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ 
+ 
+ /*
+  * percentile_disc(float8[])  - discrete percentiles
+  */
+ 
+ Datum percentile_disc_multi_final(PG_FUNCTION_ARGS);
+ 
+ struct pct_info {
+ 	int64	first_row;
+ 	int64	second_row;
+ 	float8	proportion;
+ 	int		idx;
+ };
+ 
+ static int pct_info_cmp(const void *pa, const void *pb)
+ {
+ 	const struct pct_info *a = pa;
+ 	const struct pct_info *b = pb;
+ 	if (a->first_row == b->first_row)
+ 		return (a->second_row < b->second_row) ? -1 : (a->second_row == b->second_row) ? 0 : 1;
+ 	else
+ 		return (a->first_row < b->first_row) ? -1 : 1;
+ }
+ 
+ static struct pct_info *setup_pct_info(int num_percentiles,
+ 									   Datum *percentiles_datum,
+ 									   bool *percentiles_null,
+ 									   int64 rowcount,
+ 									   bool continuous)
+ {
+ 	struct pct_info *pct_info = palloc(num_percentiles * sizeof(struct pct_info));
+ 	int		i;
+ 
+ 	for (i = 0; i < num_percentiles; i++)
+ 	{
+ 		pct_info[i].idx = i;
+ 
+ 		if (percentiles_null[i])
+ 		{
+ 			pct_info[i].first_row = 0;
+ 			pct_info[i].second_row = 0;
+ 			pct_info[i].proportion = 0;
+ 		}
+ 		else
+ 		{
+ 			float8 p = DatumGetFloat8(percentiles_datum[i]);
+ 
+ 			if (p < 0 || p > 1 || isnan(p))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 						 errmsg("percentile value %g must be between 0 and 1", p)));
+ 
+ 			if (continuous)
+ 			{
+ 				pct_info[i].first_row = 1 + floor(p * (rowcount - 1));
+ 				pct_info[i].second_row = 1 + ceil(p * (rowcount - 1));
+ 				pct_info[i].proportion = (p * (rowcount-1)) - floor(p * (rowcount-1));
+ 			}
+ 			else
+ 			{
+ 				/*
+ 				 * We need the smallest K such that (K/N) >= percentile. K starts at 1.
+ 				 * Therefore K >= N*percentile
+ 				 * Therefore K = ceil(N*percentile), minimum 1
+ 				 */
+ 
+ 				pct_info[i].first_row = Max(1, (int64) ceil(rowcount * p));
+ 				pct_info[i].second_row = 0;
+ 				pct_info[i].proportion = 0;
+ 			}
+ 		}
+ 	}
+ 
+ 	qsort(pct_info, num_percentiles, sizeof(struct pct_info), pct_info_cmp);
+ 
+ 	return pct_info;
+ }
+ 
+ Datum
+ percentile_disc_multi_final(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *param;
+ 	Datum	   *percentiles_datum;
+ 	bool	   *percentiles_null;
+ 	int			num_percentiles;
+ 	int64		rowcount = AggSetGetRowCount(fcinfo);
+ 	int64		rownum = 0;
+ 	Tuplesortstate *sorter;
+ 	Oid			datumtype;
+ 	Datum		val;
+ 	bool		isnull;
+ 	Datum	   *result_datum;
+ 	bool	   *result_isnull;
+ 	int			i;
+ 	struct pct_info *pct_info;
+ 
+ 	struct mode_type_info {
+ 		Oid typid;
+ 		int16 typLen;
+ 		bool typByVal;
+ 		char typAlign;
+ 	} *typinfo = fcinfo->flinfo->fn_extra;
+ 
+ 	if (PG_ARGISNULL(0) || rowcount < 1)
+ 		PG_RETURN_NULL();
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ 
+ 	if (!typinfo || typinfo->typid != datumtype)
+ 	{
+ 		if (typinfo)
+ 			pfree(typinfo);
+ 		typinfo = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 									 sizeof(struct mode_type_info));
+ 		typinfo->typid = datumtype;
+ 		get_typlenbyvalalign(datumtype,
+ 							 &typinfo->typLen,
+ 							 &typinfo->typByVal,
+ 							 &typinfo->typAlign);
+ 	}
+ 
+ 	param = PG_GETARG_ARRAYTYPE_P(0);
+ 
+ 	deconstruct_array(param, FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd',
+ 					  &percentiles_datum, &percentiles_null, &num_percentiles);
+ 
+ 	if (num_percentiles == 0)
+ 		PG_RETURN_POINTER(construct_empty_array(datumtype));
+ 
+ 	result_datum = palloc0(num_percentiles * sizeof(Datum));
+ 	result_isnull = palloc0(num_percentiles * sizeof(bool));
+ 
+ 	pct_info = setup_pct_info(num_percentiles,
+ 							  percentiles_datum,
+ 							  percentiles_null,
+ 							  rowcount,
+ 							  false);
+ 
+ 	/*
+ 	 * Start by dealing with any nulls in the param array - those are
+ 	 * sorted to the front on row=0, so set the corresponding result
+ 	 * indexes to null
+ 	 */
+ 	for (i = 0; i < num_percentiles; ++i)
+ 	{
+ 		int idx = pct_info[i].idx;
+ 
+ 		if (pct_info[i].first_row > 0)
+ 			break;
+ 
+ 		result_datum[idx] = (Datum) 0;
+ 		result_isnull[idx] = true;
+ 	}
+ 
+ 	/*
+ 	 * If there's anything left after doing the nulls, then grind the
+ 	 * input and extract the needed values
+ 	 */
+ 	if (i < num_percentiles)
+ 	{
+ 		tuplesort_performsort(sorter);
+ 
+ 		for (; i < num_percentiles; ++i)
+ 		{
+ 			int64 target_row = pct_info[i].first_row;
+ 			int idx = pct_info[i].idx;
+ 
+ 			if (target_row > rownum)
+ 			{
+ 				while (target_row > ++rownum)
+ 				{
+ 					if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ 						elog(ERROR,"missing row in percentile_disc");
+ 				}
+ 
+ 				if (!tuplesort_getdatum(sorter, true, &val, &isnull))
+ 					elog(ERROR,"missing row in percentile_disc");
+ 			}
+ 
+ 			result_datum[idx] = val;
+ 			result_isnull[idx] = isnull;
+ 		}
+ 	}
+ 
+ 	/* We make the output array the same shape as the input */
+ 
+ 	PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull,
+ 										 ARR_NDIM(param),
+ 										 ARR_DIMS(param), ARR_LBOUND(param),
+ 										 datumtype,
+ 										 typinfo->typLen,
+ 										 typinfo->typByVal,
+ 										 typinfo->typAlign));
+ }
+ 
+ static Datum
+ percentile_cont_multi_final_common(FunctionCallInfo fcinfo,
+ 								   Oid expect_type,
+ 								   int16 typLen, bool typByVal, char typAlign,
+ 								   LerpFunc lerpfunc)
+ {
+ 	ArrayType  *param;
+ 	Datum	   *percentiles_datum;
+ 	bool	   *percentiles_null;
+ 	int			num_percentiles;
+ 	int64		rowcount = AggSetGetRowCount(fcinfo);
+ 	int64		rownum = 0;
+ 	int64		rownum_second = 0;
+ 	Tuplesortstate *sorter;
+ 	Oid			datumtype;
+ 	Datum		first_val;
+ 	Datum		second_val;
+ 	bool		isnull;
+ 	Datum	   *result_datum;
+ 	bool	   *result_isnull;
+ 	int			i;
+ 	struct pct_info *pct_info;
+ 
+ 	if (PG_ARGISNULL(0) || rowcount < 1)
+ 		PG_RETURN_NULL();
+ 
+ 	AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ 	Assert(datumtype == expect_type);
+ 
+ 	param = PG_GETARG_ARRAYTYPE_P(0);
+ 
+ 	deconstruct_array(param, FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd',
+ 					  &percentiles_datum, &percentiles_null, &num_percentiles);
+ 
+ 	if (num_percentiles == 0)
+ 		PG_RETURN_POINTER(construct_empty_array(datumtype));
+ 
+ 	result_datum = palloc0(num_percentiles * sizeof(Datum));
+ 	result_isnull = palloc0(num_percentiles * sizeof(bool));
+ 
+ 	pct_info = setup_pct_info(num_percentiles,
+ 							  percentiles_datum,
+ 							  percentiles_null,
+ 							  rowcount,
+ 							  true);
+ 
+ 	/*
+ 	 * Start by dealing with any nulls in the param array - those are
+ 	 * sorted to the front on row=0, so set the corresponding result
+ 	 * indexes to null
+ 	 */
+ 	for (i = 0; i < num_percentiles; ++i)
+ 	{
+ 		int idx = pct_info[i].idx;
+ 
+ 		if (pct_info[i].first_row > 0)
+ 			break;
+ 
+ 		result_datum[idx] = (Datum) 0;
+ 		result_isnull[idx] = true;
+ 	}
+ 
+ 	/*
+ 	 * If there's anything left after doing the nulls, then grind the
+ 	 * input and extract the needed values
+ 	 */
+ 	if (i < num_percentiles)
+ 	{
+ 		tuplesort_performsort(sorter);
+ 
+ 		for (; i < num_percentiles; ++i)
+ 		{
+ 			int64 target_row = pct_info[i].first_row;
+ 			bool need_lerp = pct_info[i].second_row > target_row;
+ 			int idx = pct_info[i].idx;
+ 
+ 			if (target_row > rownum_second)
+ 			{
+ 				rownum = rownum_second;
+ 
+ 				while (target_row > ++rownum)
+ 				{
+ 					if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ 						elog(ERROR,"missing row in percentile_cont");
+ 				}
+ 
+ 				if (!tuplesort_getdatum(sorter, true, &first_val, &isnull) || isnull)
+ 					elog(ERROR,"missing row in percentile_cont");
+ 
+ 				rownum_second = rownum;
+ 
+ 				if (need_lerp)
+ 				{
+ 					if (!tuplesort_getdatum(sorter, true, &second_val, &isnull) || isnull)
+ 						elog(ERROR,"missing row in percentile_cont");
+ 					++rownum_second;
+ 				}
+ 			}
+ 			else if (target_row == rownum_second)
+ 			{
+ 				first_val = second_val;
+ 				rownum = rownum_second;
+ 
+ 				if (need_lerp)
+ 				{
+ 					if (!tuplesort_getdatum(sorter, true, &second_val, &isnull) || isnull)
+ 						elog(ERROR,"missing row in percentile_cont");
+ 					++rownum_second;
+ 				}
+ 			}
+ 
+ 			if (need_lerp)
+ 			{
+ 				result_datum[idx] = lerpfunc(first_val, second_val, pct_info[i].proportion);
+ 			}
+ 			else
+ 				result_datum[idx] = first_val;
+ 
+ 			result_isnull[idx] = false;
+ 		}
+ 	}
+ 
+ 	/* We make the output array the same shape as the input */
+ 
+ 	PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull,
+ 										 ARR_NDIM(param),
+ 										 ARR_DIMS(param), ARR_LBOUND(param),
+ 										 expect_type,
+ 										 typLen,
+ 										 typByVal,
+ 										 typAlign));
+ }
+ 
+ 
+ /*
+  * percentile_cont(float8[]) within group (float8)  - continuous percentiles
+  */
+ 
+ Datum percentile_cont_float8_multi_final(PG_FUNCTION_ARGS);
+ Datum percentile_cont_interval_multi_final(PG_FUNCTION_ARGS);
+ 
+ Datum
+ percentile_cont_float8_multi_final(PG_FUNCTION_ARGS)
+ {
+ 	return percentile_cont_multi_final_common(fcinfo,
+ 											  FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd',
+ 											  float8_lerp);
+ }
+ 
+ /*
+  * percentile_cont(float8[]) within group (Interval)  - continuous percentiles
+  */
+ 
+ Datum
+ percentile_cont_interval_multi_final(PG_FUNCTION_ARGS)
+ {
+ 	return percentile_cont_multi_final_common(fcinfo,
+ 											  INTERVALOID, 16, false, 'd',
+ 											  interval_lerp);
+ }
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 22,27 ****
--- 22,28 ----
  #include "access/sysattr.h"
  #include "catalog/dependency.h"
  #include "catalog/indexing.h"
+ #include "catalog/pg_aggregate.h"
  #include "catalog/pg_authid.h"
  #include "catalog/pg_collation.h"
  #include "catalog/pg_constraint.h"
***************
*** 293,298 **** static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
--- 294,302 ----
  static int print_function_arguments(StringInfo buf, HeapTuple proctup,
  						 bool print_table_args, bool print_defaults);
  static void print_function_rettype(StringInfo buf, HeapTuple proctup);
+ static void print_aggregate_arguments(StringInfo buf,
+ 									  HeapTuple proctup, HeapTuple aggtup,
+ 									  bool print_defaults);
  static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
  				 Bitmapset *rels_used);
  static bool refname_is_unique(char *refname, deparse_namespace *dpns,
***************
*** 402,407 **** static char *generate_function_name(Oid funcid, int nargs,
--- 406,413 ----
  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,2272 **** print_function_arguments(StringInfo buf, HeapTuple proctup,
--- 2273,2421 ----
  
  
  /*
+  * 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,7393 **** static void
--- 7537,7616 ----
  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,7455 **** 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, ')');
  }
  
  /*
--- 7665,7670 ----
*** a/src/backend/utils/sort/tuplesort.c
--- b/src/backend/utils/sort/tuplesort.c
***************
*** 1411,1433 **** tuplesort_performsort(Tuplesortstate *state)
   * Internal routine to fetch the next tuple in either forward or back
   * direction into *stup.  Returns FALSE if no more tuples.
   * If *should_free is set, the caller must pfree stup.tuple when done with it.
   */
  static bool
  tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  						  SortTuple *stup, bool *should_free)
  {
  	unsigned int tuplen;
  
  	switch (state->status)
  	{
  		case TSS_SORTEDINMEM:
  			Assert(forward || state->randomAccess);
! 			*should_free = false;
  			if (forward)
  			{
  				if (state->current < state->memtupcount)
  				{
! 					*stup = state->memtuples[state->current++];
  					return true;
  				}
  				state->eof_reached = true;
--- 1411,1439 ----
   * Internal routine to fetch the next tuple in either forward or back
   * direction into *stup.  Returns FALSE if no more tuples.
   * If *should_free is set, the caller must pfree stup.tuple when done with it.
+  * stup may be null to move without fetching.
   */
  static bool
  tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  						  SortTuple *stup, bool *should_free)
  {
  	unsigned int tuplen;
+ 	SortTuple dummy;
+ 	SortTuple *ptup = stup ? stup : &dummy;
  
  	switch (state->status)
  	{
  		case TSS_SORTEDINMEM:
  			Assert(forward || state->randomAccess);
! 			if (should_free)
! 				*should_free = false;
  			if (forward)
  			{
  				if (state->current < state->memtupcount)
  				{
! 					if (stup)
! 						*stup = state->memtuples[state->current];
! 					state->current++;
  					return true;
  				}
  				state->eof_reached = true;
***************
*** 1459,1479 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  					if (state->current <= 0)
  						return false;
  				}
! 				*stup = state->memtuples[state->current - 1];
  				return true;
  			}
  			break;
  
  		case TSS_SORTEDONTAPE:
  			Assert(forward || state->randomAccess);
! 			*should_free = true;
  			if (forward)
  			{
  				if (state->eof_reached)
  					return false;
  				if ((tuplen = getlen(state, state->result_tape, true)) != 0)
  				{
! 					READTUP(state, stup, state->result_tape, tuplen);
  					return true;
  				}
  				else
--- 1465,1489 ----
  					if (state->current <= 0)
  						return false;
  				}
! 				if (stup)
! 					*stup = state->memtuples[state->current - 1];
  				return true;
  			}
  			break;
  
  		case TSS_SORTEDONTAPE:
  			Assert(forward || state->randomAccess);
! 			if (should_free)
! 				*should_free = true;
  			if (forward)
  			{
  				if (state->eof_reached)
  					return false;
  				if ((tuplen = getlen(state, state->result_tape, true)) != 0)
  				{
! 					READTUP(state, ptup, state->result_tape, tuplen);
! 					if (!stup && dummy.tuple)
! 						pfree(dummy.tuple);
  					return true;
  				}
  				else
***************
*** 1546,1557 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  									  state->result_tape,
  									  tuplen))
  				elog(ERROR, "bogus tuple length in backward scan");
! 			READTUP(state, stup, state->result_tape, tuplen);
  			return true;
  
  		case TSS_FINALMERGE:
  			Assert(forward);
! 			*should_free = true;
  
  			/*
  			 * This code should match the inner loop of mergeonerun().
--- 1556,1570 ----
  									  state->result_tape,
  									  tuplen))
  				elog(ERROR, "bogus tuple length in backward scan");
! 			READTUP(state, ptup, state->result_tape, tuplen);
! 			if (!stup && dummy.tuple)
! 				pfree(dummy.tuple);
  			return true;
  
  		case TSS_FINALMERGE:
  			Assert(forward);
! 			if (should_free)
! 				*should_free = true;
  
  			/*
  			 * This code should match the inner loop of mergeonerun().
***************
*** 1563,1573 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
  				int			tupIndex;
  				SortTuple  *newtup;
  
! 				*stup = state->memtuples[0];
  				/* returned tuple is no longer counted in our memory space */
! 				if (stup->tuple)
  				{
! 					tuplen = GetMemoryChunkSpace(stup->tuple);
  					state->availMem += tuplen;
  					state->mergeavailmem[srcTape] += tuplen;
  				}
--- 1576,1586 ----
  				int			tupIndex;
  				SortTuple  *newtup;
  
! 				*ptup = state->memtuples[0];
  				/* returned tuple is no longer counted in our memory space */
! 				if (ptup->tuple)
  				{
! 					tuplen = GetMemoryChunkSpace(ptup->tuple);
  					state->availMem += tuplen;
  					state->mergeavailmem[srcTape] += tuplen;
  				}
***************
*** 1598,1603 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
--- 1611,1618 ----
  				newtup->tupindex = state->mergefreelist;
  				state->mergefreelist = tupIndex;
  				state->mergeavailslots[srcTape]++;
+ 				if (!stup && dummy.tuple)
+ 					pfree(dummy.tuple);
  				return true;
  			}
  			return false;
***************
*** 1620,1639 **** tuplesort_gettupleslot(Tuplesortstate *state, bool forward,
  	MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext);
  	SortTuple	stup;
  	bool		should_free;
  
! 	if (!tuplesort_gettuple_common(state, forward, &stup, &should_free))
! 		stup.tuple = NULL;
  
  	MemoryContextSwitchTo(oldcontext);
  
! 	if (stup.tuple)
  	{
! 		ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free);
  		return true;
  	}
  	else
  	{
! 		ExecClearTuple(slot);
  		return false;
  	}
  }
--- 1635,1656 ----
  	MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext);
  	SortTuple	stup;
  	bool		should_free;
+ 	bool        found;
  
! 	found = tuplesort_gettuple_common(state, forward, (slot ? &stup : NULL), &should_free);
  
  	MemoryContextSwitchTo(oldcontext);
  
! 	if (found)
  	{
! 		if (slot)
! 			ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free);
  		return true;
  	}
  	else
  	{
! 		if (slot)
! 			ExecClearTuple(slot);
  		return false;
  	}
  }
***************
*** 1692,1715 **** tuplesort_getdatum(Tuplesortstate *state, bool forward,
  	SortTuple	stup;
  	bool		should_free;
  
! 	if (!tuplesort_gettuple_common(state, forward, &stup, &should_free))
  	{
  		MemoryContextSwitchTo(oldcontext);
  		return false;
  	}
  
! 	if (stup.isnull1 || state->datumTypeByVal)
  	{
! 		*val = stup.datum1;
! 		*isNull = stup.isnull1;
! 	}
! 	else
! 	{
! 		if (should_free)
  			*val = stup.datum1;
  		else
! 			*val = datumCopy(stup.datum1, false, state->datumTypeLen);
! 		*isNull = false;
  	}
  
  	MemoryContextSwitchTo(oldcontext);
--- 1709,1735 ----
  	SortTuple	stup;
  	bool		should_free;
  
! 	if (!tuplesort_gettuple_common(state, forward, (val ? &stup : NULL), &should_free))
  	{
  		MemoryContextSwitchTo(oldcontext);
  		return false;
  	}
  
! 	if (val)
  	{
! 		if (stup.isnull1 || state->datumTypeByVal)
! 		{
  			*val = stup.datum1;
+ 			*isNull = stup.isnull1;
+ 		}
  		else
! 		{
! 			if (should_free)
! 				*val = stup.datum1;
! 			else
! 				*val = datumCopy(stup.datum1, false, state->datumTypeLen);
! 			*isNull = false;
! 		}
  	}
  
  	MemoryContextSwitchTo(oldcontext);
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 229,234 **** static void getTableData(TableInfo *tblinfo, int numTables, bool oids);
--- 229,235 ----
  static void makeTableDataInfo(TableInfo *tbinfo, bool oids);
  static void buildMatViewRefreshDependencies(Archive *fout);
  static void getTableDataFKConstraints(void);
+ static char *format_aggregate_arguments(FuncInfo *finfo, char *funcargs);
  static char *format_function_arguments(FuncInfo *finfo, char *funcargs,
  									   bool is_agg);
  static char *format_function_arguments_old(Archive *fout,
***************
*** 9363,9368 **** dumpProcLang(Archive *fout, ProcLangInfo *plang)
--- 9364,9385 ----
  }
  
  /*
+  * 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
***************
*** 11418,11432 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11435,11456 ----
  	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)
***************
*** 11442,11452 **** dumpAgg(Archive *fout, AggInfo *agginfo)
  	selectSourceSchema(fout, agginfo->aggfn.dobj.namespace->dobj.name);
  
  	/* Get aggregate-specific details */
! 	if (fout->remoteVersion >= 80400)
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "agginitval, "
  						  "'t'::boolean AS convertok, "
  						  "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs, "
--- 11466,11496 ----
  	selectSourceSchema(fout, agginfo->aggfn.dobj.namespace->dobj.name);
  
  	/* Get aggregate-specific details */
! 	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, "
***************
*** 11461,11466 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11505,11513 ----
  		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 "
***************
*** 11473,11478 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11520,11528 ----
  		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 "
***************
*** 11485,11490 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11535,11543 ----
  		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 "
***************
*** 11497,11502 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11550,11558 ----
  						  "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 "
***************
*** 11509,11514 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11565,11573 ----
  	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");
***************
*** 11516,11526 **** dumpAgg(Archive *fout, AggInfo *agginfo)
  	aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
  	aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
  	aggsortop = PQgetvalue(res, 0, i_aggsortop);
  	aggtranstype = PQgetvalue(res, 0, i_aggtranstype);
  	agginitval = PQgetvalue(res, 0, i_agginitval);
  	convertok = (PQgetvalue(res, 0, i_convertok)[0] == 't');
  
! 	if (fout->remoteVersion >= 80400)
  	{
  		/* 8.4 or later; we rely on server-side code for most of the work */
  		char	   *funcargs;
--- 11575,11599 ----
  	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 >= 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;
***************
*** 11550,11585 **** dumpAgg(Archive *fout, AggInfo *agginfo)
  	if (fout->remoteVersion >= 70300)
  	{
  		/* If using 7.3's regproc or regtype, data is already quoted */
! 		appendPQExpBuffer(details, "    SFUNC = %s,\n    STYPE = %s",
! 						  aggtransfn,
! 						  aggtranstype);
  	}
  	else if (fout->remoteVersion >= 70100)
  	{
  		/* format_type quotes, regproc does not */
! 		appendPQExpBuffer(details, "    SFUNC = %s,\n    STYPE = %s",
  						  fmtId(aggtransfn),
  						  aggtranstype);
  	}
  	else
  	{
  		/* need quotes all around */
! 		appendPQExpBuffer(details, "    SFUNC = %s,\n",
  						  fmtId(aggtransfn));
  		appendPQExpBuffer(details, "    STYPE = %s",
  						  fmtId(aggtranstype));
  	}
  
! 	if (!PQgetisnull(res, 0, i_agginitval))
  	{
! 		appendPQExpBuffer(details, ",\n    INITCOND = ");
! 		appendStringLiteralAH(details, agginitval, fout);
  	}
  
! 	if (strcmp(aggfinalfn, "-") != 0)
  	{
! 		appendPQExpBuffer(details, ",\n    FINALFUNC = %s",
! 						  aggfinalfn);
  	}
  
  	aggsortop = convertOperatorReference(fout, aggsortop);
--- 11623,11680 ----
  	if (fout->remoteVersion >= 70300)
  	{
  		/* If using 7.3's regproc or regtype, data is already quoted */
! 		/*
! 		 * either or both of SFUNC and STYPE might be missing in >90400,
! 		 * but if SFUNC is missing, then FINALFUNC will always be present,
! 		 * and if SFUNC is present then STYPE must also be present; the
! 		 * code below relies on these conditions to keep the commas in the
! 		 * right places. STRICT must be forced to false if SFUNC is present.
! 		 */
! 
! 		if (strcmp(aggtransfn,"-") != 0)
! 		{
! 			appendPQExpBuffer(details, "\n    SFUNC = %s,", aggtransfn);
! 			isstrict = false;
! 		}
! 
! 		if (strcmp(aggtranstype,"-") != 0)
! 			appendPQExpBuffer(details, "\n    STYPE = %s", aggtranstype);
! 		else
! 			has_comma = true;
  	}
  	else if (fout->remoteVersion >= 70100)
  	{
  		/* format_type quotes, regproc does not */
! 		appendPQExpBuffer(details, "\n    SFUNC = %s,\n    STYPE = %s",
  						  fmtId(aggtransfn),
  						  aggtranstype);
  	}
  	else
  	{
  		/* need quotes all around */
! 		appendPQExpBuffer(details, "\n    SFUNC = %s,\n",
  						  fmtId(aggtransfn));
  		appendPQExpBuffer(details, "    STYPE = %s",
  						  fmtId(aggtranstype));
  	}
  
! 	if (strcmp(aggfinalfn, "-") != 0)
  	{
! 		appendPQExpBuffer(details, "%s\n    FINALFUNC = %s",
! 						  (has_comma ? "" : ","),
! 						  aggfinalfn);
  	}
  
! 	if (hypothetical)
! 		appendPQExpBuffer(details, ",\n    HYPOTHETICAL");
! 
! 	if (isstrict)
! 		appendPQExpBuffer(details, ",\n    STRICT");
! 
! 	if (!PQgetisnull(res, 0, i_agginitval))
  	{
! 		appendPQExpBuffer(details, ",\n    INITCOND = ");
! 		appendStringLiteralAH(details, agginitval, fout);
  	}
  
  	aggsortop = convertOperatorReference(fout, aggsortop);
***************
*** 11589,11594 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11684,11696 ----
  						  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
  	 */
***************
*** 11596,11602 **** dumpAgg(Archive *fout, AggInfo *agginfo)
  					  fmtId(agginfo->aggfn.dobj.namespace->dobj.name),
  					  aggsig);
  
! 	appendPQExpBuffer(q, "CREATE AGGREGATE %s (\n%s\n);\n",
  					  aggfullsig, details->data);
  
  	appendPQExpBuffer(labelq, "AGGREGATE %s", aggsig);
--- 11698,11704 ----
  					  fmtId(agginfo->aggfn.dobj.namespace->dobj.name),
  					  aggsig);
  
! 	appendPQExpBuffer(q, "CREATE AGGREGATE %s (%s\n);\n",
  					  aggfullsig, details->data);
  
  	appendPQExpBuffer(labelq, "AGGREGATE %s", aggsig);
***************
*** 11625,11631 **** dumpAgg(Archive *fout, AggInfo *agginfo)
  	/*
  	 * Since there is no GRANT ON AGGREGATE syntax, we have to make the ACL
  	 * command look like a function's GRANT; in particular this affects the
! 	 * syntax for zero-argument aggregates.
  	 */
  	free(aggsig);
  	free(aggsig_tag);
--- 11727,11733 ----
  	/*
  	 * Since there is no GRANT ON AGGREGATE syntax, we have to make the ACL
  	 * command look like a function's GRANT; in particular this affects the
! 	 * syntax for zero-argument aggregates and ordered set functions.
  	 */
  	free(aggsig);
  	free(aggsig_tag);
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 72,78 **** describeAggregates(const char *pattern, bool verbose, bool showSystem)
  					  gettext_noop("Name"),
  					  gettext_noop("Result data type"));
  
! 	if (pset.sversion >= 80400)
  		appendPQExpBuffer(&buf,
  						  "  CASE WHEN p.pronargs = 0\n"
  						  "    THEN CAST('*' AS pg_catalog.text)\n"
--- 72,82 ----
  					  gettext_noop("Name"),
  					  gettext_noop("Result data type"));
  
! 	if (pset.sversion >= 90400)
! 		appendPQExpBuffer(&buf,
! 					      " pg_catalog.pg_get_aggregate_arguments(p.oid) AS \"%s\",\n",
! 						  gettext_noop("Argument data types"));
! 	else if (pset.sversion >= 80400)
  		appendPQExpBuffer(&buf,
  						  "  CASE WHEN p.pronargs = 0\n"
  						  "    THEN CAST('*' AS pg_catalog.text)\n"
***************
*** 254,260 **** describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
  					  gettext_noop("Schema"),
  					  gettext_noop("Name"));
  
! 	if (pset.sversion >= 80400)
  		appendPQExpBuffer(&buf,
  					"  pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n"
  				 "  pg_catalog.pg_get_function_arguments(p.oid) as \"%s\",\n"
--- 258,283 ----
  					  gettext_noop("Schema"),
  					  gettext_noop("Name"));
  
! 	if (pset.sversion >= 90400)
! 		appendPQExpBuffer(&buf,
! 					"  pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n"
! 				 "  CASE WHEN p.proisagg THEN pg_catalog.pg_get_aggregate_arguments(p.oid)\n"
!                  "       ELSE pg_catalog.pg_get_function_arguments(p.oid) END as \"%s\",\n"
! 						  " CASE\n"
! 						  "  WHEN p.proisagg THEN '%s'\n"
! 						  "  WHEN p.proiswindow THEN '%s'\n"
! 						  "  WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN '%s'\n"
! 						  "  ELSE '%s'\n"
! 						  " END as \"%s\"",
! 						  gettext_noop("Result data type"),
! 						  gettext_noop("Argument data types"),
! 		/* translator: "agg" is short for "aggregate" */
! 						  gettext_noop("agg"),
! 						  gettext_noop("window"),
! 						  gettext_noop("trigger"),
! 						  gettext_noop("normal"),
! 						  gettext_noop("Type"));
! 	else if (pset.sversion >= 80400)
  		appendPQExpBuffer(&buf,
  					"  pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n"
  				 "  pg_catalog.pg_get_function_arguments(p.oid) as \"%s\",\n"
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
***************
*** 32,37 ****
--- 32,40 ----
   *	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,49 **** CATALOG(pg_aggregate,2600) BKI_WITHOUT_OIDS
--- 47,55 ----
  	regproc		aggfinalfn;
  	Oid			aggsortop;
  	Oid			aggtranstype;
+ 	Oid			aggtranssortop;
+ 	int32		aggordnargs;
+ 	bool		aggisordsetfunc;
  
  #ifdef CATALOG_VARLEN			/* variable-length fields start here */
  	text		agginitval;
***************
*** 62,74 **** typedef FormData_pg_aggregate *Form_pg_aggregate;
   * ----------------
   */
  
! #define Natts_pg_aggregate				6
  #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
  
  
  /* ----------------
--- 68,83 ----
   * ----------------
   */
  
! #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_aggtranssortop	6
! #define Anum_pg_aggregate_aggordnargs	7
! #define Anum_pg_aggregate_aggisordsetfunc	8
! #define Anum_pg_aggregate_agginitval	9
  
  
  /* ----------------
***************
*** 77,239 **** 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}" ));
  
  /* 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_ ));
  
  /* 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_ ));
  
  /* 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_ ));
  
  /* count */
! DATA(insert ( 2147	int8inc_any		-				0		20		"0" ));
! DATA(insert ( 2803	int8inc			-				0		20		"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}" ));
  
  /* 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}" ));
  
  /* 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}" ));
  
  /* 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}" ));
  
  /* 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}" ));
  
  /* 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}" ));
  
  /* 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}" ));
  
  /* 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_ ));
  
  /* 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_ ));
  
  /* xml */
! DATA(insert ( 2901 xmlconcat2	  -					0	142		_null_ ));
  
  /* array */
! DATA(insert ( 2335	array_agg_transfn	array_agg_finalfn		0	2281	_null_ ));
  
  /* text */
! DATA(insert ( 3538	string_agg_transfn	string_agg_finalfn		0	2281	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	bytea_string_agg_transfn	bytea_string_agg_finalfn		0	2281	_null_ ));
  
  /* json */
! DATA(insert ( 3175	json_agg_transfn	json_agg_finalfn		0	2281	_null_ ));
  
  /*
   * prototypes for functions in pg_aggregate.c
--- 86,261 ----
   */
  
  /* avg */
! 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	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		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		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 -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 -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 -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 -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 -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 -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 -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 -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		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		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		0 -1 f _null_ ));
  
  /* array */
! 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	0 -1 f _null_ ));
  
  /* bytea */
! 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	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 ( 3968 	- 			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,246 **** DATA(insert ( 3175	json_agg_transfn	json_agg_finalfn		0	2281	_null_ ));
--- 263,269 ----
  extern Oid AggregateCreate(const char *aggName,
  				Oid aggNamespace,
  				int numArgs,
+ 				int numDirectArgs,
  				oidvector *parameterTypes,
  				Datum allParameterTypes,
  				Datum parameterModes,
***************
*** 249,255 **** extern Oid AggregateCreate(const char *aggName,
  				List *aggtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
! 				const char *agginitval);
  
  #endif   /* PG_AGGREGATE_H */
--- 272,282 ----
  				List *aggtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
+ 				List *aggtranssortopName,
  				Oid aggTransType,
! 				const char *agginitval,
! 				bool isStrict,
! 				bool isOrderedSetFunc,
! 				bool isHypotheticalSet);
  
  #endif   /* PG_AGGREGATE_H */
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 1964,1969 **** DATA(insert OID = 2232 (  pg_get_function_identity_arguments	   PGNSP PGUID 12 1
--- 1964,1973 ----
  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");
***************
*** 4729,4734 **** DESCR("SP-GiST support for quad tree over range");
--- 4733,4806 ----
  /* 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 = 3968 ( rank		PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_	aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("hypothetical rank");
+ 
+ DATA(insert OID = 3969 ( rank_final		PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_	hypothetical_rank_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3970 ( dense_rank		PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_	aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("rank of hypothetical row without gaps");
+ 
+ DATA(insert OID = 3971 ( dense_rank_final		PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_	hypothetical_dense_rank_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3972 ( percent_rank		PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_	aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("fractional ranking of hypothetical row within a group");
+ 
+ DATA(insert OID = 3973 ( percent_rank_final		PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_	hypothetical_percent_rank_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3974 ( cume_dist		PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_	aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("cumulative distribution of hypothetical row in a group");
+ 
+ DATA(insert OID = 3975 ( cume_dist_final		PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_	hypothetical_cume_dist_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3976 ( mode		PGNSP PGUID 12 1 0 0 0 t f f f t f i 1 0 2283 2283 _null_ _null_ _null_ _null_	aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("most common value in group");
+ DATA(insert OID = 3977 ( mode_final		PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2283 2283 _null_ _null_ _null_ _null_	mode_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3978 ( percentile_disc	PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 2277 "1022 2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("multiple discrete percentiles");
+ 
+ DATA(insert OID = 3979 ( percentile_disc_multi_final	PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2277 "1022 2283" _null_ _null_ _null_ _null_ percentile_disc_multi_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3980 ( percentile_cont	PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 1022 "1022 701" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("multiple continuous percentiles of float8 values");
+ 
+ DATA(insert OID = 3981 ( percentile_cont_float8_multi_final	PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1022 "1022 701" _null_ _null_ _null_ _null_ percentile_cont_float8_multi_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
+ DATA(insert OID = 3982 ( percentile_cont	PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 1187 "1022 1186" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("multiple continuous percentiles of interval values");
+ 
+ DATA(insert OID = 3983 ( percentile_cont_interval_multi_final	PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1187 "1022 1186" _null_ _null_ _null_ _null_ percentile_cont_interval_multi_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+ 
  /*
   * Symbolic values for provolatile column: these indicate whether the result
   * of a function is dependent *only* on the values of its explicit arguments,
*** a/src/include/fmgr.h
--- b/src/include/fmgr.h
***************
*** 651,656 **** extern void **find_rendezvous_variable(const char *varName);
--- 651,685 ----
  extern int AggCheckCallContext(FunctionCallInfo fcinfo,
  					MemoryContext *aggcontext);
  
+ typedef struct Tuplesortstate fmTuplesortstate;
+ typedef struct tupleDesc *fmTupleDesc;
+ typedef struct TupleTableSlot fmTupleTableSlot;
+ 
+ extern int64 AggSetGetRowCount(FunctionCallInfo fcinfo);
+ 
+ extern void AggSetGetSortInfo(FunctionCallInfo fcinfo,
+ 							  fmTuplesortstate **sortstate,
+ 							  fmTupleDesc *tupdesc,
+ 							  fmTupleTableSlot **tupslot,
+ 							  Oid *datumtype);
+ 
+ /* int16 rather than AttrNumber here to avoid includes */
+ extern int AggSetGetDistinctInfo(FunctionCallInfo fcinfo,
+ 								 fmTupleTableSlot **tupslot,
+ 								 int16 **sortColIdx,
+ 								 FmgrInfo **equalfns);
+ 
+ /* int16 rather than AttrNumber here to avoid includes */
+ extern int AggSetGetSortOperators(FunctionCallInfo fcinfo,
+ 								  int16 **sortColIdx,
+ 								  Oid **sortOperators,
+ 								  Oid **sortEqOperators,
+ 								  Oid **sortCollations,
+ 								  bool **sortNullsFirst);
+ 
+ extern void AggSetGetPerTupleContext(FunctionCallInfo fcinfo,
+ 						 MemoryContext *memcontext);
+ 
  /*
   * We allow plugin modules to hook function entry/exit.  This is intended
   * as support for loadable security policy modules, which may want to
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 588,593 **** typedef struct AggrefExprState
--- 588,594 ----
  {
  	ExprState	xprstate;
  	List	   *args;			/* states of argument expressions */
+ 	List	   *orddirectargs;	/* Ordered direct arguments */
  	ExprState  *aggfilter;		/* FILTER expression */
  	int			aggno;			/* ID number for agg within its plan node */
  } AggrefExprState;
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 425,431 **** typedef enum NodeTag
  	T_WindowObjectData,			/* private in nodeWindowAgg.c */
  	T_TIDBitmap,				/* in nodes/tidbitmap.h */
  	T_InlineCodeBlock,			/* in nodes/parsenodes.h */
! 	T_FdwRoutine				/* in foreign/fdwapi.h */
  } NodeTag;
  
  /*
--- 425,432 ----
  	T_WindowObjectData,			/* private in nodeWindowAgg.c */
  	T_TIDBitmap,				/* in nodes/tidbitmap.h */
  	T_InlineCodeBlock,			/* in nodes/parsenodes.h */
! 	T_FdwRoutine,				/* in foreign/fdwapi.h */
! 	T_AggStatePerAggData			/* private in nodeAgg.c */
  } NodeTag;
  
  /*
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 303,308 **** typedef struct FuncCall
--- 303,309 ----
  	bool		agg_star;		/* argument was really '*' */
  	bool		agg_distinct;	/* arguments were labeled DISTINCT */
  	bool		func_variadic;	/* last argument was labeled VARIADIC */
+ 	bool		has_within_group;	/* WITHIN GROUP clause,if any */
  	struct WindowDef *over;		/* OVER clause, if any */
  	int			location;		/* token location, or -1 if unknown */
  } FuncCall;
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 247,255 **** typedef struct Aggref
--- 247,258 ----
  	List	   *args;			/* arguments and sort expressions */
  	List	   *aggorder;		/* ORDER BY (list of SortGroupClause) */
  	List	   *aggdistinct;	/* DISTINCT (list of SortGroupClause) */
+ 	List	   *orddirectargs;	/* Direct arguments for ordered set functions */
  	Expr	   *aggfilter;		/* FILTER expression */
  	bool		aggstar;		/* TRUE if argument list was really '*' */
  	bool		aggvariadic;	/* TRUE if VARIADIC was used in call */
+ 	bool		isordset;	/* If node is from an ordered set function */
+ 	bool		ishypothetical;	/* If node is from a hypothetical set function */
  	Index		agglevelsup;	/* > 0 if agg belongs to outer query */
  	int			location;		/* token location, or -1 if unknown */
  } Aggref;
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 412,417 **** PG_KEYWORD("where", WHERE, RESERVED_KEYWORD)
--- 412,418 ----
  PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("window", WINDOW, RESERVED_KEYWORD)
  PG_KEYWORD("with", WITH, RESERVED_KEYWORD)
+ PG_KEYWORD("within", WITHIN, UNRESERVED_KEYWORD)
  PG_KEYWORD("without", WITHOUT, UNRESERVED_KEYWORD)
  PG_KEYWORD("work", WORK, UNRESERVED_KEYWORD)
  PG_KEYWORD("wrapper", WRAPPER, UNRESERVED_KEYWORD)
*** a/src/include/parser/parse_agg.h
--- b/src/include/parser/parse_agg.h
***************
*** 17,23 ****
  
  extern void transformAggregateCall(ParseState *pstate, Aggref *agg,
  					   List *args, List *aggorder,
! 					   bool agg_distinct);
  extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
  						WindowDef *windef);
  
--- 17,23 ----
  
  extern void transformAggregateCall(ParseState *pstate, Aggref *agg,
  					   List *args, List *aggorder,
! 					   bool agg_distinct, bool agg_within_group);
  extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
  						WindowDef *windef);
  
***************
*** 34,37 **** extern void build_aggregate_fnexprs(Oid *agg_input_types,
--- 34,51 ----
  						Expr **transfnexpr,
  						Expr **finalfnexpr);
  
+ void
+ build_orderedset_fnexprs(Oid *agg_input_types,
+ 					int agg_num_inputs,
+ 					bool agg_variadic,
+ 					Oid agg_result_type,
+ 					Oid agg_input_collation,
+ 					Oid *agg_input_collation_array,
+ 					Oid finalfn_oid,
+ 					Expr **finalfnexpr);
+ 
+ int get_aggregate_argtypes(Aggref *aggref, 
+ 				Oid *inputTypes, 
+ 				Oid *inputCollations);
+ 
  #endif   /* PARSE_AGG_H */
*** a/src/include/parser/parse_clause.h
--- b/src/include/parser/parse_clause.h
***************
*** 31,37 **** extern List *transformGroupClause(ParseState *pstate, List *grouplist,
  					 ParseExprKind exprKind, bool useSQL99);
  extern List *transformSortClause(ParseState *pstate, List *orderlist,
  					List **targetlist, ParseExprKind exprKind,
! 					bool resolveUnknown, bool useSQL99);
  
  extern List *transformWindowDefinitions(ParseState *pstate,
  						   List *windowdefs,
--- 31,37 ----
  					 ParseExprKind exprKind, bool useSQL99);
  extern List *transformSortClause(ParseState *pstate, List *orderlist,
  					List **targetlist, ParseExprKind exprKind,
! 					bool resolveUnknown, bool useSQL99, bool keepDuplicates);
  
  extern List *transformWindowDefinitions(ParseState *pstate,
  						   List *windowdefs,
*** a/src/include/parser/parse_func.h
--- b/src/include/parser/parse_func.h
***************
*** 38,51 **** typedef enum
  	FUNCDETAIL_NORMAL,			/* found a matching regular function */
  	FUNCDETAIL_AGGREGATE,		/* found a matching aggregate function */
  	FUNCDETAIL_WINDOWFUNC,		/* found a matching window function */
! 	FUNCDETAIL_COERCION			/* it's a type coercion request */
  } FuncDetailCode;
  
- 
  extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
! 				  List *agg_order, Expr *agg_filter,
! 				  bool agg_star, bool agg_distinct, bool func_variadic,
! 				  WindowDef *over, bool is_column, int location);
  
  extern FuncDetailCode func_get_detail(List *funcname,
  				List *fargs, List *fargnames,
--- 38,48 ----
  	FUNCDETAIL_NORMAL,			/* found a matching regular function */
  	FUNCDETAIL_AGGREGATE,		/* found a matching aggregate function */
  	FUNCDETAIL_WINDOWFUNC,		/* found a matching window function */
! 	FUNCDETAIL_COERCION,			/* it's a type coercion request */
  } FuncDetailCode;
  
  extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
! 							   int location, FuncCall *fn);
  
  extern FuncDetailCode func_get_detail(List *funcname,
  				List *fargs, List *fargnames,
***************
*** 66,73 **** extern FuncCandidateList func_select_candidate(int nargs,
  
  extern void make_fn_arguments(ParseState *pstate,
  				  List *fargs,
  				  Oid *actual_arg_types,
! 				  Oid *declared_arg_types);
  
  extern const char *funcname_signature_string(const char *funcname, int nargs,
  						  List *argnames, const Oid *argtypes);
--- 63,72 ----
  
  extern void make_fn_arguments(ParseState *pstate,
  				  List *fargs,
+ 				  List *agg_order,
  				  Oid *actual_arg_types,
! 				  Oid *declared_arg_types,
! 				  bool requiresUnification);
  
  extern const char *funcname_signature_string(const char *funcname, int nargs,
  						  List *argnames, const Oid *argtypes);
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 658,663 **** extern Datum pg_get_functiondef(PG_FUNCTION_ARGS);
--- 658,665 ----
  extern Datum pg_get_function_arguments(PG_FUNCTION_ARGS);
  extern Datum pg_get_function_identity_arguments(PG_FUNCTION_ARGS);
  extern Datum pg_get_function_result(PG_FUNCTION_ARGS);
+ extern Datum pg_get_aggregate_arguments(PG_FUNCTION_ARGS);
+ extern Datum pg_get_aggregate_identity_arguments(PG_FUNCTION_ARGS);
  extern char *deparse_expression(Node *expr, List *dpcontext,
  				   bool forceprefix, bool showimplicit);
  extern List *deparse_context_for(const char *aliasname, Oid relid);
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
***************
*** 1249,1254 **** select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
--- 1249,1461 ----
   {"(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:  Incorrect number of arguments for hypothetical set function
+ 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     
*** a/src/test/regress/expected/opr_sanity.out
--- b/src/test/regress/expected/opr_sanity.out
***************
*** 700,708 **** SELECT * FROM funcdescs
  
  -- **************** pg_aggregate ****************
  -- Look for illegal values in pg_aggregate fields.
  SELECT ctid, aggfnoid::oid
  FROM pg_aggregate as p1
! WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0;
   ctid | aggfnoid 
  ------+----------
  (0 rows)
--- 700,716 ----
  
  -- **************** pg_aggregate ****************
  -- Look for illegal values in pg_aggregate fields.
+ -- ordered set functions can't have transfns, and must
+ -- have finalfns, but may or may not have transtypes.
+ -- other aggs must have transfns and transtypes with
+ -- optional finalfns.
  SELECT ctid, aggfnoid::oid
  FROM pg_aggregate as p1
! WHERE aggfnoid = 0
!    OR CASE WHEN aggisordsetfunc
!            THEN aggtransfn <> 0 OR aggfinalfn = 0
!            ELSE aggtransfn = 0 OR aggtranstype = 0
!       END;
   ctid | aggfnoid 
  ------+----------
  (0 rows)
***************
*** 764,771 **** WHERE a.aggfnoid = p.oid AND
      a.aggfinalfn = pfn.oid AND
      (pfn.proretset
       OR NOT binary_coercible(pfn.prorettype, p.prorettype)
!      OR pfn.pronargs != 1
!      OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]));
   aggfnoid | proname | oid | proname 
  ----------+---------+-----+---------
  (0 rows)
--- 772,780 ----
      a.aggfinalfn = pfn.oid AND
      (pfn.proretset
       OR NOT binary_coercible(pfn.prorettype, p.prorettype)
!      OR (aggisordsetfunc IS FALSE
!          AND (pfn.pronargs != 1
!               OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]))));
   aggfnoid | proname | oid | proname 
  ----------+---------+-----+---------
  (0 rows)
***************
*** 857,866 **** ORDER BY 1;
   count("any") | count()
  (1 row)
  
! -- For the same reason, we avoid creating built-in variadic aggregates.
! SELECT oid, proname
! FROM pg_proc AS p
! WHERE proisagg AND provariadic != 0;
   oid | proname 
  -----+---------
  (0 rows)
--- 866,877 ----
   count("any") | count()
  (1 row)
  
! -- For the same reason, we avoid creating built-in variadic aggregates, except
! -- ordered set functions (which have their own syntax and are not subject to
! -- the misplaced ORDER BY issue).
! SELECT p.oid, proname
! FROM pg_proc AS p JOIN pg_aggregate AS a ON (a.aggfnoid=p.oid)
! WHERE proisagg AND provariadic != 0 AND NOT a.aggisordsetfunc;
   oid | proname 
  -----+---------
  (0 rows)
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
***************
*** 481,486 **** select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
--- 481,549 ----
      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;
*** a/src/test/regress/sql/opr_sanity.sql
--- b/src/test/regress/sql/opr_sanity.sql
***************
*** 564,573 **** SELECT * FROM funcdescs
  -- **************** pg_aggregate ****************
  
  -- Look for illegal values in pg_aggregate fields.
  
  SELECT ctid, aggfnoid::oid
  FROM pg_aggregate as p1
! WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0;
  
  -- Make sure the matching pg_proc entry is sensible, too.
  
--- 564,581 ----
  -- **************** pg_aggregate ****************
  
  -- Look for illegal values in pg_aggregate fields.
+ -- ordered set functions can't have transfns, and must
+ -- have finalfns, but may or may not have transtypes.
+ -- other aggs must have transfns and transtypes with
+ -- optional finalfns.
  
  SELECT ctid, aggfnoid::oid
  FROM pg_aggregate as p1
! WHERE aggfnoid = 0
!    OR CASE WHEN aggisordsetfunc
!            THEN aggtransfn <> 0 OR aggfinalfn = 0
!            ELSE aggtransfn = 0 OR aggtranstype = 0
!       END;
  
  -- Make sure the matching pg_proc entry is sensible, too.
  
***************
*** 618,625 **** WHERE a.aggfnoid = p.oid AND
      a.aggfinalfn = pfn.oid AND
      (pfn.proretset
       OR NOT binary_coercible(pfn.prorettype, p.prorettype)
!      OR pfn.pronargs != 1
!      OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]));
  
  -- If transfn is strict then either initval should be non-NULL, or
  -- input type should match transtype so that the first non-null input
--- 626,634 ----
      a.aggfinalfn = pfn.oid AND
      (pfn.proretset
       OR NOT binary_coercible(pfn.prorettype, p.prorettype)
!      OR (aggisordsetfunc IS FALSE
!          AND (pfn.pronargs != 1
!               OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]))));
  
  -- If transfn is strict then either initval should be non-NULL, or
  -- input type should match transtype so that the first non-null input
***************
*** 685,695 **** WHERE p1.oid < p2.oid AND p1.proname = p2.proname AND
      array_dims(p1.proargtypes) != array_dims(p2.proargtypes)
  ORDER BY 1;
  
! -- For the same reason, we avoid creating built-in variadic aggregates.
  
! SELECT oid, proname
! FROM pg_proc AS p
! WHERE proisagg AND provariadic != 0;
  
  -- For the same reason, built-in aggregates with default arguments are no good.
  
--- 694,706 ----
      array_dims(p1.proargtypes) != array_dims(p2.proargtypes)
  ORDER BY 1;
  
! -- For the same reason, we avoid creating built-in variadic aggregates, except
! -- ordered set functions (which have their own syntax and are not subject to
! -- the misplaced ORDER BY issue).
  
! SELECT p.oid, proname
! FROM pg_proc AS p JOIN pg_aggregate AS a ON (a.aggfnoid=p.oid)
! WHERE proisagg AND provariadic != 0 AND NOT a.aggisordsetfunc;
  
  -- For the same reason, built-in aggregates with default arguments are no good.
  
#24Robert Haas
robertmhaas@gmail.com
In reply to: Andrew Gierth (#22)
Re: Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

On Sat, Sep 14, 2013 at 7:23 AM, Andrew Gierth
<andrew@tao11.riddles.org.uk> wrote:

Peter> Please fix compiler warnings:

Someone should do the same in WaitForBackgroundWorkerStartup so that
building with -Werror works.

I don't get a warning there. Can you be more specific about the problem?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#25Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Robert Haas (#24)
Re: Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

"Robert" == Robert Haas <robertmhaas@gmail.com> writes:

Someone should do the same in WaitForBackgroundWorkerStartup so
that building with -Werror works.

Robert> I don't get a warning there. Can you be more specific about
Robert> the problem?

bgworker.c: In function 'WaitForBackgroundWorkerStartup':
bgworker.c:866: warning: 'pid' may be used uninitialized in this function

gcc 4.2.2 / freebsd 8.2

--
Andrew (irc:RhodiumToad)

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#26Robert Haas
robertmhaas@gmail.com
In reply to: Andrew Gierth (#25)
1 attachment(s)
Re: Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

On Tue, Sep 17, 2013 at 2:27 PM, Andrew Gierth
<andrew@tao11.riddles.org.uk> wrote:

"Robert" == Robert Haas <robertmhaas@gmail.com> writes:

Someone should do the same in WaitForBackgroundWorkerStartup so
that building with -Werror works.

Robert> I don't get a warning there. Can you be more specific about
Robert> the problem?

bgworker.c: In function 'WaitForBackgroundWorkerStartup':
bgworker.c:866: warning: 'pid' may be used uninitialized in this function

Does the attached patch fix it for you?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

bgworker-wait-fix.patchapplication/octet-stream; name=bgworker-wait-fix.patchDownload
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 25e6156..0e7a4a5 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -863,7 +863,6 @@ BgwHandleStatus
 WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pidp)
 {
 	BgwHandleStatus	status;
-	pid_t	pid;
 	int		rc;
 	bool	save_set_latch_on_sigusr1;
 
@@ -874,9 +873,13 @@ WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pidp)
 	{
 		for (;;)
 		{
+			pid_t	pid;
+
 			CHECK_FOR_INTERRUPTS();
 
 			status = GetBackgroundWorkerPid(handle, &pid);
+			if (status == BGWH_STARTED)
+				*pidp = pid;
 			if (status != BGWH_NOT_YET_STARTED)
 				break;
 
@@ -900,6 +903,5 @@ WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pidp)
 	PG_END_TRY();
 
 	set_latch_on_sigusr1 = save_set_latch_on_sigusr1;
-	*pidp = pid;
 	return status;
 }
#27Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Robert Haas (#26)
Re: Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

"Robert" == Robert Haas <robertmhaas@gmail.com> writes:

bgworker.c: In function 'WaitForBackgroundWorkerStartup':
bgworker.c:866: warning: 'pid' may be used uninitialized in this function

Robert> Does the attached patch fix it for you?

It compiles without error and looks ok...

--
Andrew (irc:RhodiumToad)

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#28Robert Haas
robertmhaas@gmail.com
In reply to: Andrew Gierth (#27)
Re: Re: Proposal/design feedback needed: WITHIN GROUP (sql standard ordered set aggregate functions)

On Thu, Sep 19, 2013 at 12:52 PM, Andrew Gierth
<andrew@tao11.riddles.org.uk> wrote:

It compiles without error and looks ok...

Thanks for checking. Committed.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers