Re: WITHIN GROUP patch
2013/10/9 Pavel Stehule <pavel.stehule@gmail.com>
Show quoted text
Hello
I checked a conformance with ANSI SQL - and I didn't find any issue.
I found so following error message is not too friendly (mainly because
this functionality will be new)postgres=# select dense_rank(3,3,2) within group (order by num desc, odd)
from test4;
ERROR: Incorrect number of arguments for hypothetical set function
LINE 1: select dense_rank(3,3,2) within group (order by num desc, od...
^
postgres=# select dense_rank(3,3,2) within group (order by num desc) from
test4;
ERROR: Incorrect number of arguments for hypothetical set function
LINE 1: select dense_rank(3,3,2) within group (order by num desc) fr...
^
postgres=# select dense_rank(3,3) within group (order by num desc) from
test4;
ERROR: Incorrect number of arguments for hypothetical set function
LINE 1: select dense_rank(3,3) within group (order by num desc) from...
^
postgres=# select dense_rank(3,3) within group (order by num desc, num)
from test4;
dense_rank
------------
3
(1 row)Probably some hint should be there?
Regards
Pavel
2013/10/2 Vik Fearing <vik.fearing@dalibo.com>
On 09/30/2013 06:34 PM, Pavel Stehule wrote:
I looked on this patch - it is one from long patches - so I propose to
divide review to a few parts:a) a conformance with ANSI SQL
b) check of new aggregates - semantic, implementation
c) source code checking - usual patch reviewNow I would to work on @a
I had an unexpected emergency come up, sorry about that. I plan on
doing B and C starting on Thursday (October 3).I am grateful to have Pavel's help, this is a big patch.
--
Vik
Import Notes
Reply to msg id not found: CAFj8pRB1dQ-jAxDpy+Me6UBUYUFKh_4wr-jhxVdO5h847Azw@mail.gmail.comReference msg id not found: CAESHdJrh3Eh-2PcR_KzLMU5h-JUZxoYjf-mGX5M7StArT-Jg@mail.gmail.comReference msg id not found: CAFj8pRD63ybsbBeeHU0iKLj+a3fSnUWqLUywZz_jTqLAay9wWg@mail.gmail.comReference msg id not found: 524BE81F.3030601@dalibo.comReference msg id not found: CAFj8pRB1dQ-jAxDpy+Me6UBUYUFKh_4wr-jhxVdO5h847Azw@mail.gmail.com
"Pavel" == Pavel Stehule <pavel.stehule@gmail.com> writes:
I found so following error message is not too friendly (mainly because
this functionality will be new)postgres=# select dense_rank(3,3,2) within group (order by num desc, odd)
from test4;
ERROR: Incorrect number of arguments for hypothetical set function
LINE 1: select dense_rank(3,3,2) within group (order by num desc, od...
^Probably some hint should be there?
Hm, yeah.
Anyone have ideas for better wording for the original error message,
or a DETAIL or HINT addition? The key point here is that the number of
_hypothetical_ arguments and the number of ordered columns must match,
but we don't currently exclude the possibility that a user-defined
hypothetical set function might have additional non-hypothetical args.
The first alternative that springs to mind is:
ERROR: Incorrect number of arguments for hypothetical set function
DETAIL: Number of hypothetical arguments (3) must equal number of ordered columns (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
2013/10/11 Andrew Gierth <andrew@tao11.riddles.org.uk>
"Pavel" == Pavel Stehule <pavel.stehule@gmail.com> writes:
I found so following error message is not too friendly (mainly because
this functionality will be new)postgres=# select dense_rank(3,3,2) within group (order by num desc,
odd)
from test4;
ERROR: Incorrect number of arguments for hypothetical set function
LINE 1: select dense_rank(3,3,2) within group (order by num desc, od...
^Probably some hint should be there?
Hm, yeah.
Anyone have ideas for better wording for the original error message,
or a DETAIL or HINT addition? The key point here is that the number of
_hypothetical_ arguments and the number of ordered columns must match,
but we don't currently exclude the possibility that a user-defined
hypothetical set function might have additional non-hypothetical args.The first alternative that springs to mind is:
ERROR: Incorrect number of arguments for hypothetical set function
DETAIL: Number of hypothetical arguments (3) must equal number of ordered
columns (2)+1
Pavel
Show quoted text
--
Andrew (irc:RhodiumToad)
On Thu, Oct 10, 2013 at 10:35 PM, Andrew Gierth
<andrew@tao11.riddles.org.uk> wrote:
The first alternative that springs to mind is:
ERROR: Incorrect number of arguments for hypothetical set function
DETAIL: Number of hypothetical arguments (3) must equal number of ordered columns (2)
I'd suggest trying to collapse that down into a single message; the
DETAIL largely recapitulates the original error message. Also, I'd
suggest identifying the name of the function, since people may not be
able to identify that easily based just on the fact that it's a
hypothetical set function.
Here's one idea, with two variants depending on the direction of the inequality:
ERROR: function "%s" has %d arguments but only %d ordering columns
ERROR: function "%s" has %d ordering columns but only %d arguments
Or else leave out the actual numbers and just state the principle, but
identifying the exact function at fault, e.g.
ERROR: number of arguments to function "%s" does not match number of
ordering columns
--
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
On 10/09/2013 04:19 PM, Pavel Stehule wrote:
I checked a conformance with ANSI SQL - and I didn't find any issue.
I found so following error message is not too friendly (mainly
because this functionality will be new)postgres=# select dense_rank(3,3,2) within group (order by num
desc, odd) from test4;
ERROR: Incorrect number of arguments for hypothetical set function
LINE 1: select dense_rank(3,3,2) within group (order by num desc,
od...
^
postgres=# select dense_rank(3,3,2) within group (order by num
desc) from test4;
ERROR: Incorrect number of arguments for hypothetical set function
LINE 1: select dense_rank(3,3,2) within group (order by num desc)
fr...
^
postgres=# select dense_rank(3,3) within group (order by num desc)
from test4;
ERROR: Incorrect number of arguments for hypothetical set function
LINE 1: select dense_rank(3,3) within group (order by num desc)
from...
^
postgres=# select dense_rank(3,3) within group (order by num desc,
num) from test4;
dense_rank
------------
3
(1 row)Probably some hint should be there?
In addition to Pavel's review, I have finally finished reading the
patch. Here are some notes, mainly on style:
First of all, it no longer compiles on HEAD because commit
4d212bac1752e1bad6f3aa6242061c393ae93a0a stole oid 3968. I modified
that locally to be able to continue my review.
Some of the error messages do not comply with project style. That is,
they begin with a capital letter.
Ordered set functions cannot have transition functions
Ordered set functions must have final functions
Invalid argument types for hypothetical set function
Invalid argument types for ordered set function
Incompatible change to aggregate definition
Too many arguments to ordered set function
Ordered set finalfns must not be strict
Cannot have multiple ORDER BY clauses with WITHIN GROUP
Cannot have DISTINCT and WITHIN GROUP together
Incorrect number of arguments for hypothetical set function
Incorrect number of direct arguments to ordered set function %s
And in pg_aggregate.c I found a comment with a similar problem that
doesn't match its surrounding code:
Oid transsortop = InvalidOid; /* Can be omitted */
I didn't find any more examples like that, but I did see several block
comments that weren't complete sentences whereas I think they should
be. Also a lot of the code comments say "I" and I don't recall seeing
that elsewhere. I may be wrong, but I would prefer if they were more
neutral.
The documentation has a number of issues.
collateindex.pl complains of duplicated index entries for PERCENTILE
CONTINUOUS and PERCENTILE DISCRETE. This is because the index markup is
used for both overloaded versions. This is the same mistake Bruce made
and then corrected in commit 5dcc48c2c76cf4b2b17c8e14fe3e588ae0c8eff3.
"if there are multiple equally good result" should have an s on the end
in func.sgml.
Table 9-49 has an extra empty column. That should either be removed, or
filled in with some kind of comment text like other similar tables.
Apart from that, it looks good. There is some mismatched coding styles
in there but the next run of pgindent should catch them so it's no big deal.
I haven't yet exercised the actual functionality of the new functions,
nor have I tried to create my own. Andrew alerted me to a division by
zero bug in one of them, so I'll be looking forward to catching that.
So, more review to come.
--
Vik
On Tue, Oct 15, 2013 at 4:29 PM, Vik Fearing <vik.fearing@dalibo.com> wrote:
On 10/09/2013 04:19 PM, Pavel Stehule wrote:
I checked a conformance with ANSI SQL - and I didn't find any issue.
I found so following error message is not too friendly (mainly because
this functionality will be new)postgres=# select dense_rank(3,3,2) within group (order by num desc, odd)
from test4;
ERROR: Incorrect number of arguments for hypothetical set function
LINE 1: select dense_rank(3,3,2) within group (order by num desc, od...
^
postgres=# select dense_rank(3,3,2) within group (order by num desc) from
test4;
ERROR: Incorrect number of arguments for hypothetical set function
LINE 1: select dense_rank(3,3,2) within group (order by num desc) fr...
^
postgres=# select dense_rank(3,3) within group (order by num desc) from
test4;
ERROR: Incorrect number of arguments for hypothetical set function
LINE 1: select dense_rank(3,3) within group (order by num desc) from...
^
postgres=# select dense_rank(3,3) within group (order by num desc, num)
from test4;
dense_rank
------------
3
(1 row)Probably some hint should be there?
In addition to Pavel's review, I have finally finished reading the patch.
Here are some notes, mainly on style:First of all, it no longer compiles on HEAD because commit
4d212bac1752e1bad6f3aa6242061c393ae93a0a stole oid 3968. I modified that
locally to be able to continue my review.Some of the error messages do not comply with project style. That is, they
begin with a capital letter.Ordered set functions cannot have transition functions
Ordered set functions must have final functions
Invalid argument types for hypothetical set function
Invalid argument types for ordered set function
Incompatible change to aggregate definition
Too many arguments to ordered set function
Ordered set finalfns must not be strict
Cannot have multiple ORDER BY clauses with WITHIN GROUP
Cannot have DISTINCT and WITHIN GROUP togetherIncorrect number of arguments for hypothetical set function
Incorrect number of direct arguments to ordered set function %sAnd in pg_aggregate.c I found a comment with a similar problem that doesn't
match its surrounding code:
Oid transsortop = InvalidOid; /* Can be omitted */I didn't find any more examples like that, but I did see several block
comments that weren't complete sentences whereas I think they should be.
Also a lot of the code comments say "I" and I don't recall seeing that
elsewhere. I may be wrong, but I would prefer if they were more neutral.The documentation has a number of issues.
collateindex.pl complains of duplicated index entries for PERCENTILE
CONTINUOUS and PERCENTILE DISCRETE. This is because the index markup is
used for both overloaded versions. This is the same mistake Bruce made and
then corrected in commit 5dcc48c2c76cf4b2b17c8e14fe3e588ae0c8eff3."if there are multiple equally good result" should have an s on the end in
func.sgml.Table 9-49 has an extra empty column. That should either be removed, or
filled in with some kind of comment text like other similar tables.Apart from that, it looks good. There is some mismatched coding styles in
there but the next run of pgindent should catch them so it's no big deal.I haven't yet exercised the actual functionality of the new functions, nor
have I tried to create my own. Andrew alerted me to a division by zero bug
in one of them, so I'll be looking forward to catching that.So, more review to come.
--
Vik
Hi All,
Please find attached our latest version of the patch. This version
fixes the issues pointed out by the reviewers.
Regards,
Atri
--
Regards,
Atri
l'apprenant
Attachments:
withingrouppatch41113.patchtext/x-diff; charset=US-ASCII; name=withingrouppatch41113.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9af4697..083c75e 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -352,7 +352,7 @@
<entry><structfield>aggtransfn</structfield></entry>
<entry><type>regproc</type></entry>
<entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry>Transition function</entry>
+ <entry>Transition function (zero if none)</entry>
</row>
<row>
<entry><structfield>aggfinalfn</structfield></entry>
@@ -370,7 +370,25 @@
<entry><structfield>aggtranstype</structfield></entry>
<entry><type>oid</type></entry>
<entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
- <entry>Data type of the aggregate function's internal transition (state) data</entry>
+ <entry>Data type of the aggregate function's internal transition (state) data (zero if none)</entry>
+ </row>
+ <row>
+ <entry><structfield>aggtranssortop</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.oid</literal></entry>
+ <entry>An optional sort operator for the type "aggtranstype", used for some kinds of ordered set functions</entry>
+ </row>
+ <row>
+ <entry><structfield>aggordnargs</structfield></entry>
+ <entry><type>int4</type></entry>
+ <entry></entry>
+ <entry>Number of direct arguments to ordered set function; -2 for hypothetical set functions; -1 for ordinary aggregates.</entry>
+ </row>
+ <row>
+ <entry><structfield>aggisordsetfunc</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>A flag to represent whether a function is ordered set or not</entry>
</row>
<row>
<entry><structfield>agginitval</structfield></entry>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index a1d3aee..9ed7c5c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -12252,6 +12252,257 @@ SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab;
</sect1>
+ <sect1 id="functions-ordered">
+ <title>Ordered Set Functions</title>
+
+ <indexterm zone="functions-ordered">
+ <primary>ordered set function</primary>
+ <secondary>built-in</secondary>
+ </indexterm>
+
+ <para>
+ <firstterm>Ordered set functions</firstterm> compute a single result
+ from an ordered set of input values. The built-in ordered set functions
+ are listed in
+ <xref linkend="functions-inversedist-table"> and
+ <xref linkend="functions-hypothetical-table">.
+ The special syntax considerations for ordered set functions
+ are explained in <xref linkend="syntax-orderedset">.
+ </para>
+
+ <table id="functions-inversedist-table">
+ <title>Inverse Distribution Functions</title>
+
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Function</entry>
+ <entry>Direct Argument Type(s)</entry>
+ <entry>Ordered Argument Type(s)</entry>
+ <entry>Return Type</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>percentile</primary>
+ <secondary>discrete</secondary>
+ </indexterm>
+ <function>percentile_disc(<replaceable class="parameter">fraction</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>double precision</type> (must be [0..1])
+ </entry>
+ <entry>
+ any sortable type
+ </entry>
+ <entry>
+ same as sort expression
+ </entry>
+ <entry>
+ discrete percentile; returns the first result whose position in
+ the ordering equals or exceeds the specified fraction
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>percentile</primary>
+ <secondary>discrete</secondary>
+ </indexterm>
+ <function>percentile_disc(<replaceable class="parameter">fractions</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>double precision[]</type> (all must be [0..1] or null)
+ </entry>
+ <entry>
+ any sortable type
+ </entry>
+ <entry>
+ array of input type
+ </entry>
+ <entry>
+ multiple discrete percentile; returns an array of results matching the
+ shape of the <literal>fractions</literal> parameter, with each
+ non-null element replaced by the input value at that percentile
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>percentile</primary>
+ <secondary>continuous</secondary>
+ </indexterm>
+ <indexterm>
+ <primary>median</primary>
+ </indexterm>
+ <function>percentile_cont(<replaceable class="parameter">fraction</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>double precision</type> (must be [0..1])
+ </entry>
+ <entry>
+ <type>double precision</type> or <type>interval</type>
+ </entry>
+ <entry>
+ same as sort expression
+ </entry>
+ <entry>
+ continuous percentile; interpolates between adjacent items.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>percentile</primary>
+ <secondary>continuous</secondary>
+ </indexterm>
+ <function>percentile_cont(<replaceable class="parameter">fractions</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>double precision[]</type> (all must be [0..1] or null)
+ </entry>
+ <entry>
+ <type>double precision</type> or <type>interval</type>
+ </entry>
+ <entry>
+ array of input type
+ </entry>
+ <entry>
+ multiple continuous percentile; returns an array of results matching
+ the shape of the <literal>fractions</literal> parameter, with each
+ non-null element replaced by the value corresponding to that percentile
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>mode</primary>
+ <secondary>statistical</secondary>
+ </indexterm>
+ <function>mode() WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+ </entry>
+ <entry>
+ </entry>
+ <entry>
+ any sortable type
+ </entry>
+ <entry>
+ same as sort expression
+ </entry>
+ <entry>
+ returns the most frequent input value (choosing one arbitrarily if
+ there are multiple equally good result)
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ All the inverse distribution functions ignore null values in their sorted
+ input. The <replaceable>fraction</replaceable> parameter must be between 0
+ and 1; an error is thrown if not. However, a null fraction simply produces
+ a null result.
+ </para>
+
+ <table id="functions-hypothetical-table">
+ <title>Hypothetical Set Functions</title>
+
+ <tgroup cols="3">
+ <thead>
+ <row>
+ <entry>Function</entry>
+ <entry>Return Type</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>rank</primary>
+ <secondary>hypothetical</secondary>
+ </indexterm>
+ <function>rank(<replaceable class="parameter">args</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sorted_args</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>bigint</type>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>dense_rank</primary>
+ <secondary>hypothetical</secondary>
+ </indexterm>
+ <function>dense_rank(<replaceable class="parameter">args</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sorted_args</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>bigint</type>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>percent_rank</primary>
+ <secondary>hypothetical</secondary>
+ </indexterm>
+ <function>percent_rank(<replaceable class="parameter">args</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sorted_args</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>double precision</type>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>cume_dist</primary>
+ <secondary>hypothetical</secondary>
+ </indexterm>
+ <function>cume_dist(<replaceable class="parameter">args</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sorted_args</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>double precision</type>
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ For all hypothetical set functions, the list of arguments given
+ by <replaceable>args</replaceable> should match the number and types of
+ arguments given as <replaceable>sorted_args</replaceable>.
+ </para>
+
+ <para>
+ All of the functions listed in
+ <xref linkend="functions-hypothetical-table"> are associated with a
+ window function defined in
+ <xref linkend="functions-window">. In each case, the function result
+ represents the value that the associated window function would have
+ returned, for the hypothetical row constructed from
+ <replaceable>args</replaceable> and included in the sorted group of
+ rows.
+ </para>
+
+ </sect1>
+
<sect1 id="functions-window">
<title>Window Functions</title>
diff --git a/doc/src/sgml/ref/alter_aggregate.sgml b/doc/src/sgml/ref/alter_aggregate.sgml
index aab5b2b..4fb3b6f 100644
--- a/doc/src/sgml/ref/alter_aggregate.sgml
+++ b/doc/src/sgml/ref/alter_aggregate.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
+ALTER AGGREGATE
RENAME TO <replaceable>new_name</replaceable>
ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
OWNER TO <replaceable>new_owner</replaceable>
ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
SET SCHEMA <replaceable>new_schema</replaceable>
+
+<phrase>where <replaceable>aggregate_signature</replaceable> is one of:</phrase>
+
+<replaceable>name</replaceable> ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
+<replaceable>name</replaceable> ( [ [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] ] ) WITHIN GROUP ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
</synopsis>
</refsynopsisdiv>
@@ -148,10 +153,11 @@ ALTER AGGREGATE myavg(integer) OWNER TO joe;
</para>
<para>
- To move the aggregate function <literal>myavg</literal> for type
- <type>integer</type> into schema <literal>myschema</literal>:
+ To move the ordered set function <literal>mypercentile</literal> with
+ direct argument of type <type>float8</type> taking groups
+ of <type>integer</type> type into schema <literal>myschema</literal>:
<programlisting>
-ALTER AGGREGATE myavg(integer) SET SCHEMA myschema;
+ALTER AGGREGATE mypercentile(float8) WITHIN GROUP (integer) SET SCHEMA myschema;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml
index a14fcb4..5d326ae 100644
--- a/doc/src/sgml/ref/alter_extension.sgml
+++ b/doc/src/sgml/ref/alter_extension.sgml
@@ -30,7 +30,7 @@ ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> DROP <replacea
<phrase>where <replaceable class="PARAMETER">member_object</replaceable> is:</phrase>
- AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) |
+ AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) [ WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) ] |
CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
CONVERSION <replaceable class="PARAMETER">object_name</replaceable> |
diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml
index e550500..c62fa44 100644
--- a/doc/src/sgml/ref/comment.sgml
+++ b/doc/src/sgml/ref/comment.sgml
@@ -23,7 +23,7 @@ PostgreSQL documentation
<synopsis>
COMMENT ON
{
- AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) |
+ AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) [ WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) ] |
CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
COLUMN <replaceable class="PARAMETER">relation_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
diff --git a/doc/src/sgml/ref/create_aggregate.sgml b/doc/src/sgml/ref/create_aggregate.sgml
index 2b35fa4..45b67c3 100644
--- a/doc/src/sgml/ref/create_aggregate.sgml
+++ b/doc/src/sgml/ref/create_aggregate.sgml
@@ -29,6 +29,15 @@ CREATE AGGREGATE <replaceable class="parameter">name</replaceable> ( [ <replacea
[ , SORTOP = <replaceable class="PARAMETER">sort_operator</replaceable> ]
)
+CREATE AGGREGATE <replaceable class="parameter">name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">arg_name</replaceable> ] <replaceable class="parameter">arg_data_type</replaceable> [ , ... ] ) WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">arg_name</replaceable> ] <replaceable class="parameter">arg_data_type</replaceable> [ , ... ] ) (
+ FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable>
+ [ , STRICT ]
+ [ , HYPOTHETICAL ]
+ [ , STYPE = <replaceable class="PARAMETER">state_data_type</replaceable> ]
+ [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
+ [ , TRANSSORTOP = <replaceable class="PARAMETER">state_sort_operator</replaceable> ]
+)
+
<phrase>or the old syntax</phrase>
CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
@@ -70,7 +79,7 @@ CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
</para>
<para>
- An aggregate function is made from one or two ordinary
+ An ordinary aggregate function is made from one or two ordinary
functions:
a state transition function
<replaceable class="PARAMETER">sfunc</replaceable>,
@@ -165,6 +174,14 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
</para>
<para>
+ The <literal>WITHIN GROUP</literal> syntax denotes a special subset of
+ aggregate functions collectively called <quote>ordered set
+ functions</quote>. These functions operate over groups of sorted values
+ in order-dependent ways. As such, they are constructed differently; there
+ is no state transition function, but the final function is required.
+ </para>
+
+ <para>
To be able to create an aggregate function, you must
have <literal>USAGE</literal> privilege on the argument types, the state
type, and the return type, as well as <literal>EXECUTE</literal> privilege
@@ -278,6 +295,11 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
aggregate's result, and the return type is <replaceable
class="PARAMETER">state_data_type</replaceable>.
</para>
+ <para>
+ For ordered set functions, the function arguments must instead
+ correspond to the input arguments (both direct and grouped) plus
+ the state type if any.
+ </para>
</listitem>
</varlistentry>
@@ -305,6 +327,39 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">state_sort_operator</replaceable></term>
+ <listitem>
+ <para>
+ For ordered set functions only, this is a sort operator that can be
+ applied to
+ the <replaceable class="PARAMETER">state_data_type</replaceable>.
+ This is just an operator name (possibly schema-qualified).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>STRICT</literal></term>
+ <listitem>
+ <para>
+ For ordered set functions only, this flag specifies that the function is
+ strict, i.e. that grouped rows containing nulls are skipped.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>HYPOTHETICAL</literal></term>
+ <listitem>
+ <para>
+ For ordered set functions only, this flag specifies that the aggregate
+ parameters are to be processed according to the requirements for
+ hypothetical set functions.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
<para>
diff --git a/doc/src/sgml/ref/drop_aggregate.sgml b/doc/src/sgml/ref/drop_aggregate.sgml
index 06060fb..2c5ab01 100644
--- a/doc/src/sgml/ref/drop_aggregate.sgml
+++ b/doc/src/sgml/ref/drop_aggregate.sgml
@@ -21,9 +21,13 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP AGGREGATE [ IF EXISTS ]
- <replaceable class="parameter">name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">arg_name</replaceable> ] <replaceable class="parameter">arg_data_type</replaceable> [ , ... ] )
+DROP AGGREGATE [ IF EXISTS ] <replaceable class="parameter">aggregate_signature</replaceable>
[ CASCADE | RESTRICT ]
+
+<phrase>where <replaceable>aggregate_signature</replaceable> is one of:</phrase>
+
+<replaceable>name</replaceable> ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
+<replaceable>name</replaceable> ( [ [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] ] ) WITHIN GROUP ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/security_label.sgml b/doc/src/sgml/ref/security_label.sgml
index 76c131f..ebca07b 100644
--- a/doc/src/sgml/ref/security_label.sgml
+++ b/doc/src/sgml/ref/security_label.sgml
@@ -25,7 +25,7 @@ SECURITY LABEL [ FOR <replaceable class="PARAMETER">provider</replaceable> ] ON
{
TABLE <replaceable class="PARAMETER">object_name</replaceable> |
COLUMN <replaceable class="PARAMETER">table_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
- AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) |
+ AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) [ WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) ] |
DATABASE <replaceable class="PARAMETER">object_name</replaceable> |
DOMAIN <replaceable class="PARAMETER">object_name</replaceable> |
EVENT TRIGGER <replaceable class="PARAMETER">object_name</replaceable> |
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index e3dbc4b..c3d26e8 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -1706,6 +1706,54 @@ SELECT string_agg(a ORDER BY a, ',') FROM table; -- incorrect
</para>
</sect2>
+ <sect2 id="syntax-orderedset">
+ <title>Ordered Set Functions</title>
+
+ <indexterm zone="syntax-orderedset">
+ <primary>ordered set function</primary>
+ </indexterm>
+
+ <indexterm zone="syntax-orderedset">
+ <primary>aggregate function</primary>
+ <secondary>ordered set function</secondary>
+ </indexterm>
+
+ <indexterm zone="syntax-orderedset">
+ <primary>WITHIN GROUP</primary>
+ </indexterm>
+
+ <para>
+ An <firstterm>ordered set function</firstterm> is a particular kind of
+ aggregate function which is applied to sorted groups of values and returns
+ a single result for each group which may be influenced by the sort
+ order. Like all aggregate functions, it reduces multiple inputs to a
+ single output value; typical ordered set functions return a percentile
+ extracted from the ordered group, or the rank a specified value would have
+ within that group. The syntax of an ordered set function is:
+
+<synopsis>
+<replaceable>function_name</replaceable> ( [ <replaceable>expression</replaceable> [ , ... ] ] ) WITHIN GROUP ( <replaceable>order_by_clause</replaceable> ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+</synopsis>
+
+ where <replaceable>function_name</replaceable> is a previously
+ defined ordered set function (possibly qualified with a schema name) and
+ <replaceable>expression</replaceable> is any value expression that does
+ not itself contain an aggregate expression, a window function call, or any
+ reference to ungrouped columns of the source data. The
+ mandatory <replaceable>order_by_clause</replaceable> has the same syntax
+ as for a query-level <literal>ORDER BY</> clause, as described
+ in <xref linkend="queries-order">, except that its expressions are always
+ just expressions and cannot be output-column names or numbers. The
+ expressions of the <replaceable>order_by_clause</replaceable> may, and
+ almost invariably do, refer to the ungrouped columns of the input; the
+ clause defines the grouped input to the function. The optional
+ <replaceable>filter_clause</replaceable> is identical to that for
+ aggregate functions (see <xref linkend="syntax-aggregates">, and is applied
+ to input rows prior to the sort operation.
+ </para>
+
+ </sect2>
+
<sect2 id="syntax-window-functions">
<title>Window Function Calls</title>
diff --git a/doc/src/sgml/xaggr.sgml b/doc/src/sgml/xaggr.sgml
index 9ed7d99..fc51602 100644
--- a/doc/src/sgml/xaggr.sgml
+++ b/doc/src/sgml/xaggr.sgml
@@ -9,20 +9,17 @@
</indexterm>
<para>
- Aggregate functions in <productname>PostgreSQL</productname>
- are expressed in terms of <firstterm>state values</firstterm>
- and <firstterm>state transition functions</firstterm>.
- That is, an aggregate operates using a state value that is updated
- as each successive input row is processed.
- To define a new aggregate
- function, one selects a data type for the state value,
- an initial value for the state, and a state transition
- function. The state transition function is just an
- ordinary function that could also be used outside the
- context of the aggregate. A <firstterm>final function</firstterm>
- can also be specified, in case the desired result of the aggregate
- is different from the data that needs to be kept in the running
- state value.
+ Aggregate functions (other than ordered set functions)
+ in <productname>PostgreSQL</productname> are expressed in terms
+ of <firstterm>state values</firstterm> and <firstterm>state transition
+ functions</firstterm>. That is, an aggregate operates using a state value
+ that is updated as each successive input row is processed. To define a new
+ aggregate function, one selects a data type for the state value, an initial
+ value for the state, and a state transition function. The state transition
+ function is just an ordinary function that could also be used outside the
+ context of the aggregate. A <firstterm>final function</firstterm> can also
+ be specified, in case the desired result of the aggregate is different from
+ the data that needs to be kept in the running state value.
</para>
<para>
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index d9e961e..bb44113 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -46,6 +46,7 @@ Oid
AggregateCreate(const char *aggName,
Oid aggNamespace,
int numArgs,
+ int numDirectArgs,
oidvector *parameterTypes,
Datum allParameterTypes,
Datum parameterModes,
@@ -54,24 +55,29 @@ AggregateCreate(const char *aggName,
List *aggtransfnName,
List *aggfinalfnName,
List *aggsortopName,
+ List *aggtranssortopName,
Oid aggTransType,
- const char *agginitval)
+ const char *agginitval,
+ bool isStrict,
+ bool isOrderedSet,
+ bool isHypotheticalSet)
{
Relation aggdesc;
HeapTuple tup;
bool nulls[Natts_pg_aggregate];
Datum values[Natts_pg_aggregate];
Form_pg_proc proc;
- Oid transfn;
+ Oid transfn = InvalidOid; /* can be omitted */
Oid finalfn = InvalidOid; /* can be omitted */
Oid sortop = InvalidOid; /* can be omitted */
+ Oid transsortop = InvalidOid; /* Can be omitted */
Oid *aggArgTypes = parameterTypes->values;
bool hasPolyArg;
bool hasInternalArg;
+ Oid variadic_type = InvalidOid;
Oid rettype;
Oid finaltype;
- Oid *fnArgs;
- int nargs_transfn;
+ Oid *fnArgs = palloc((numArgs + 1) * sizeof(Oid));
Oid procOid;
TupleDesc tupDesc;
int i;
@@ -83,8 +89,20 @@ AggregateCreate(const char *aggName,
if (!aggName)
elog(ERROR, "no aggregate name supplied");
- if (!aggtransfnName)
- elog(ERROR, "aggregate must have a transition function");
+ if (isOrderedSet)
+ {
+ if (aggtransfnName)
+ elog(ERROR, "ordered set functions cannot have transition functions");
+ if (!aggfinalfnName)
+ elog(ERROR, "ordered set functions must have final functions");
+ }
+ else
+ {
+ if (!aggtransfnName)
+ elog(ERROR, "aggregate must have a transition function");
+ if (isStrict)
+ elog(ERROR, "aggregate with transition function must not be explicitly STRICT");
+ }
/* check for polymorphic and INTERNAL arguments */
hasPolyArg = false;
@@ -97,6 +115,136 @@ AggregateCreate(const char *aggName,
hasInternalArg = true;
}
+ /*-
+ * Argument mode checks. If there were no variadics, we should have been
+ * passed a NULL pointer for parameterModes, so we can skip this if so.
+ * Otherwise, the allowed cases are as follows:
+ *
+ * aggfn(..., variadic sometype) - normal agg with variadic arg last
+ * aggfn(..., variadic "any") - normal agg with "any" variadic
+ *
+ * ordfn(..., variadic "any") within group (*)
+ * - ordered set func with "any" variadic in direct args, which requires
+ * that the ordered args also be variadic any which we represent
+ * specially; this is the common case for hypothetical set functions.
+ * Note this is the only case where numDirectArgs == numArgs on input
+ * (implies finalfn(..., variadic "any"))
+ *
+ * ordfn(...) within group (..., variadic "any")
+ * - ordered set func with no variadic in direct args, but allowing any
+ * types of ordered args.
+ * (implies finalfn(..., ..., variadic "any"))
+ *
+ * We don't allow variadic ordered args other than "any"; we don't allow
+ * anything after variadic "any" except the special-case (*).
+ *
+ * We might like to support this one:
+ *
+ * ordfn(..., variadic sometype) within group (...)
+ * - ordered set func with variadic direct arg last, followed by ordered
+ * args, none of which are variadic
+ * (implies finalfn(..., sometype, ..., [transtype]))
+ *
+ * but currently it seems to be too intrusive to do so; the assumption
+ * that variadic args can only come last is quite widespread.
+ */
+
+ if (parameterModes != PointerGetDatum(NULL))
+ {
+ /*
+ * We expect the array to be a 1-D CHAR array; verify that. We don't
+ * need to use deconstruct_array() since the array data is just going
+ * to look like a C array of char values.
+ */
+ ArrayType *modesArray = (ArrayType *) DatumGetPointer(parameterModes);
+ char *paramModes;
+ int modesCount;
+ int i;
+
+ if (ARR_NDIM(modesArray) != 1 ||
+ ARR_HASNULL(modesArray) ||
+ ARR_ELEMTYPE(modesArray) != CHAROID)
+ elog(ERROR, "parameterModes is not a 1-D char array");
+
+ paramModes = (char *) ARR_DATA_PTR(modesArray);
+ modesCount = ARR_DIMS(modesArray)[0];
+
+ for (i = 0; i < modesCount; ++i)
+ {
+ switch (paramModes[i])
+ {
+ case PROARGMODE_VARIADIC:
+ if (OidIsValid(variadic_type))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("VARIADIC must not be specified more than once")));
+ variadic_type = aggArgTypes[i];
+
+ /* enforce restrictions on ordered args */
+
+ if (numDirectArgs >= 0
+ && i >= numDirectArgs
+ && variadic_type != ANYOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("VARIADIC ordered arguments must be of type ANY")));
+
+ break;
+
+ case PROARGMODE_IN:
+ if (OidIsValid(variadic_type))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("VARIADIC argument must be last")));
+ break;
+
+ default:
+ elog(ERROR, "invalid argument mode");
+ }
+ }
+ }
+
+ switch (variadic_type)
+ {
+ case InvalidOid:
+ case ANYARRAYOID:
+ case ANYOID:
+ /* okay */
+ break;
+ default:
+ if (!OidIsValid(get_element_type(variadic_type)))
+ elog(ERROR, "VARIADIC parameter must be an array");
+ break;
+ }
+
+ if (isHypotheticalSet)
+ {
+ if (numArgs != numDirectArgs
+ || variadic_type != ANYOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("invalid argument types for hypothetical set function"),
+ errhint("Required declaration is (..., VARIADIC \"any\") WITHIN GROUP (*)")));
+
+ /* flag for special processing for hypothetical sets */
+ numDirectArgs = -2;
+ }
+ else if (numArgs == numDirectArgs)
+ {
+ if (variadic_type == ANYOID)
+ {
+ /*
+ * this case allows the number of direct args to be truly variable
+ */
+ numDirectArgs = -1;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("invalid argument types for ordered set function"),
+ errhint("WITHIN GROUP (*) is not allowed without VARIADIC \"any\"")));
+ }
+
/*
* If transtype is polymorphic, must have polymorphic argument also; else
* we will have no way to deduce the actual transtype.
@@ -107,53 +255,86 @@ AggregateCreate(const char *aggName,
errmsg("cannot determine transition data type"),
errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument.")));
- /* find the transfn */
- nargs_transfn = numArgs + 1;
- fnArgs = (Oid *) palloc(nargs_transfn * sizeof(Oid));
- fnArgs[0] = aggTransType;
- memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
- transfn = lookup_agg_function(aggtransfnName, nargs_transfn, fnArgs,
- &rettype);
+ if (!isOrderedSet)
+ {
+ /* find the transfn */
- /*
- * Return type of transfn (possibly after refinement by
- * enforce_generic_type_consistency, if transtype isn't polymorphic) must
- * exactly match declared transtype.
- *
- * In the non-polymorphic-transtype case, it might be okay to allow a
- * rettype that's binary-coercible to transtype, but I'm not quite
- * convinced that it's either safe or useful. When transtype is
- * polymorphic we *must* demand exact equality.
- */
- if (rettype != aggTransType)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("return type of transition function %s is not %s",
- NameListToString(aggtransfnName),
- format_type_be(aggTransType))));
+ fnArgs[0] = aggTransType;
+ memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
- tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn));
- if (!HeapTupleIsValid(tup))
- elog(ERROR, "cache lookup failed for function %u", transfn);
- proc = (Form_pg_proc) GETSTRUCT(tup);
+ transfn = lookup_agg_function(aggtransfnName, numArgs + 1, fnArgs,
+ &rettype);
- /*
- * If the transfn is strict and the initval is NULL, make sure first input
- * type and transtype are the same (or at least binary-compatible), so
- * that it's OK to use the first input value as the initial transValue.
- */
- if (proc->proisstrict && agginitval == NULL)
- {
- if (numArgs < 1 ||
- !IsBinaryCoercible(aggArgTypes[0], aggTransType))
+ /*
+ * Return type of transfn (possibly after refinement by
+ * enforce_generic_type_consistency, if transtype isn't polymorphic)
+ * must exactly match declared transtype.
+ *
+ * In the non-polymorphic-transtype case, it might be okay to allow a
+ * rettype that's binary-coercible to transtype, but I'm not quite
+ * convinced that it's either safe or useful. When transtype is
+ * polymorphic we *must* demand exact equality.
+ */
+ if (rettype != aggTransType)
ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("return type of transition function %s is not %s",
+ NameListToString(aggtransfnName),
+ format_type_be(aggTransType))));
+
+ tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for function %u", transfn);
+ proc = (Form_pg_proc) GETSTRUCT(tup);
+
+ /*
+ * If the transfn is strict and the initval is NULL, make sure first
+ * input type and transtype are the same (or at least
+ * binary-compatible), so that it's OK to use the first input value as
+ * the initial transValue.
+ */
+ if (proc->proisstrict && agginitval == NULL)
+ {
+ if (numArgs < 1 ||
+ !IsBinaryCoercible(aggArgTypes[0], aggTransType))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
+ }
+ ReleaseSysCache(tup);
}
- ReleaseSysCache(tup);
/* handle finalfn, if supplied */
- if (aggfinalfnName)
+ if (isOrderedSet)
+ {
+ int num_final_args = numArgs;
+
+ memcpy(fnArgs, aggArgTypes, num_final_args * sizeof(Oid));
+
+ /*
+ * If there's a transtype, it becomes the last arg to the finalfn;
+ * but if the agg (and hence the finalfn) is variadic "any", then
+ * this contributes nothing to the signature.
+ */
+ if (aggTransType != InvalidOid && variadic_type != ANYOID)
+ fnArgs[num_final_args++] = aggTransType;
+
+ finalfn = lookup_agg_function(aggfinalfnName, num_final_args, fnArgs,
+ &finaltype);
+
+ /*
+ * this is also checked at runtime for security reasons, but check
+ * here too to provide a friendly error (the requirement is because
+ * the finalfn will be passed null dummy args for type resolution
+ * purposes)
+ */
+
+ if (func_strict(finalfn))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("ordered set final functions must not be declared STRICT")));
+ }
+ else if (aggfinalfnName)
{
fnArgs[0] = aggTransType;
finalfn = lookup_agg_function(aggfinalfnName, 1, fnArgs,
@@ -166,6 +347,7 @@ AggregateCreate(const char *aggName,
*/
finaltype = aggTransType;
}
+
Assert(OidIsValid(finaltype));
/*
@@ -207,6 +389,18 @@ AggregateCreate(const char *aggName,
false, -1);
}
+ /* handle transsortop, if supplied */
+ if (aggtranssortopName)
+ {
+ if (!isOrderedSet || !OidIsValid(aggTransType))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("transition sort operator can only be specified for ordered set functions with transition types")));
+ transsortop = LookupOperName(NULL, aggtranssortopName,
+ aggTransType, aggTransType,
+ false, -1);
+ }
+
/*
* permission checks on used types
*/
@@ -217,15 +411,17 @@ AggregateCreate(const char *aggName,
aclcheck_error_type(aclresult, aggArgTypes[i]);
}
- aclresult = pg_type_aclcheck(aggTransType, GetUserId(), ACL_USAGE);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error_type(aclresult, aggTransType);
+ if (OidIsValid(aggTransType))
+ {
+ aclresult = pg_type_aclcheck(aggTransType, GetUserId(), ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error_type(aclresult, aggTransType);
+ }
aclresult = pg_type_aclcheck(finaltype, GetUserId(), ACL_USAGE);
if (aclresult != ACLCHECK_OK)
aclcheck_error_type(aclresult, finaltype);
-
/*
* Everything looks okay. Try to create the pg_proc entry for the
* aggregate. (This could fail if there's already a conflicting entry.)
@@ -246,7 +442,7 @@ AggregateCreate(const char *aggName,
false, /* security invoker (currently not
* definable for agg) */
false, /* isLeakProof */
- false, /* isStrict (not needed for agg) */
+ isStrict, /* isStrict (needed for ordered set funcs) */
PROVOLATILE_IMMUTABLE, /* volatility (not
* needed for agg) */
parameterTypes, /* paramTypes */
@@ -272,7 +468,11 @@ AggregateCreate(const char *aggName,
values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
+ values[Anum_pg_aggregate_aggtranssortop - 1] = ObjectIdGetDatum(transsortop);
values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
+ values[Anum_pg_aggregate_aggordnargs - 1] = Int32GetDatum(numDirectArgs);
+ values[Anum_pg_aggregate_aggisordsetfunc - 1] = BoolGetDatum(isOrderedSet);
+
if (agginitval)
values[Anum_pg_aggregate_agginitval - 1] = CStringGetTextDatum(agginitval);
else
@@ -290,18 +490,23 @@ AggregateCreate(const char *aggName,
/*
* Create dependencies for the aggregate (above and beyond those already
- * made by ProcedureCreate). Note: we don't need an explicit dependency
- * on aggTransType since we depend on it indirectly through transfn.
+ * made by ProcedureCreate). Normal aggs don't need an explicit
+ * dependency on aggTransType since we depend on it indirectly through
+ * transfn, but ordered set functions with variadic "any" do need one
+ * (ordered set functions without variadic depend on it via the finalfn).
*/
myself.classId = ProcedureRelationId;
myself.objectId = procOid;
myself.objectSubId = 0;
/* Depends on transition function */
- referenced.classId = ProcedureRelationId;
- referenced.objectId = transfn;
- referenced.objectSubId = 0;
- recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ if (OidIsValid(transfn))
+ {
+ referenced.classId = ProcedureRelationId;
+ referenced.objectId = transfn;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
/* Depends on final function, if any */
if (OidIsValid(finalfn))
@@ -321,6 +526,24 @@ AggregateCreate(const char *aggName,
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
+ /* Depends on transsort operator, if any */
+ if (OidIsValid(transsortop))
+ {
+ referenced.classId = OperatorRelationId;
+ referenced.objectId = transsortop;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /* May depend on aggTransType if any */
+ if (OidIsValid(aggTransType) && isOrderedSet && variadic_type == ANYOID)
+ {
+ referenced.classId = TypeRelationId;
+ referenced.objectId = aggTransType;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
return procOid;
}
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 78af092..9774667 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -44,9 +44,12 @@
* DefineAggregate
*
* "oldstyle" signals the old (pre-8.2) style where the aggregate input type
- * is specified by a BASETYPE element in the parameters. Otherwise,
- * "args" is a list of FunctionParameter structs defining the agg's arguments.
- * "parameters" is a list of DefElem representing the agg's definition clauses.
+ * is specified by a BASETYPE element in the parameters. Otherwise, "args" is
+ * a pair, whose first element is a list of FunctionParameter structs defining
+ * the agg's arguments (both direct and ordered), and whose second element is
+ * an Integer node with the number of direct args, or -1 if this isn't an
+ * ordered set func. "parameters" is a list of DefElem representing the agg's
+ * definition clauses.
*/
Oid
DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
@@ -58,18 +61,23 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
List *transfuncName = NIL;
List *finalfuncName = NIL;
List *sortoperatorName = NIL;
+ List *transsortoperatorName = NIL;
TypeName *baseType = NULL;
TypeName *transType = NULL;
char *initval = NULL;
int numArgs;
+ int numDirectArgs = -1;
+ Oid transTypeId = InvalidOid;
oidvector *parameterTypes;
ArrayType *allParameterTypes;
ArrayType *parameterModes;
ArrayType *parameterNames;
List *parameterDefaults;
- Oid transTypeId;
char transTypeType;
ListCell *pl;
+ bool ishypothetical = false;
+ bool isOrderedSet = false;
+ bool isStrict = false;
/* Convert list of names to a name and namespace */
aggNamespace = QualifiedNameGetCreationNamespace(name, &aggName);
@@ -80,6 +88,14 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
get_namespace_name(aggNamespace));
+ Assert(args == NIL || list_length(args) == 2);
+
+ if (list_length(args) == 2)
+ {
+ numDirectArgs = intVal(lsecond(args));
+ isOrderedSet = (numDirectArgs != -1);
+ }
+
foreach(pl, parameters)
{
DefElem *defel = (DefElem *) lfirst(pl);
@@ -106,6 +122,12 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
initval = defGetString(defel);
else if (pg_strcasecmp(defel->defname, "initcond1") == 0)
initval = defGetString(defel);
+ else if (pg_strcasecmp(defel->defname, "hypothetical") == 0)
+ ishypothetical = true;
+ else if (pg_strcasecmp(defel->defname, "strict") == 0)
+ isStrict = true;
+ else if (pg_strcasecmp(defel->defname, "transsortop") == 0)
+ transsortoperatorName = defGetQualifiedName(defel);
else
ereport(WARNING,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -113,17 +135,35 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
defel->defname)));
}
- /*
- * make sure we have our required definitions
- */
- if (transType == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("aggregate stype must be specified")));
- if (transfuncName == NIL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("aggregate sfunc must be specified")));
+ if (!isOrderedSet)
+ {
+ /*
+ * make sure we have our required definitions
+ */
+ if (transType == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("aggregate stype must be specified")));
+ if (transfuncName == NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("aggregate sfunc must be specified")));
+ if (isStrict)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("aggregate with sfunc must not be explicitly declared STRICT")));
+ }
+ else
+ {
+ if (transfuncName != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("sfunc must not be specified for ordered set functions")));
+ if (finalfuncName == NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("finalfunc must be specified for ordered set functions")));
+ }
/*
* look up the aggregate's input datatype(s).
@@ -173,8 +213,15 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("basetype is redundant with aggregate input type specification")));
- numArgs = list_length(args);
- interpret_function_parameter_list(args,
+ /*
+ * The grammar has already concatenated the direct and ordered
+ * args (if any) for us. Note that error checking for position
+ * and number of VARIADIC args is not done for us, we have to
+ * do it ourselves later (in AggregateCreate)
+ */
+
+ numArgs = list_length(linitial(args));
+ interpret_function_parameter_list(linitial(args),
InvalidOid,
true, /* is an aggregate */
queryString,
@@ -191,7 +238,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
}
/*
- * look up the aggregate's transtype.
+ * look up the aggregate's transtype, if specified.
*
* transtype can't be a pseudo-type, since we need to be able to store
* values of the transtype. However, we can allow polymorphic transtype
@@ -201,18 +248,20 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
* worse) by connecting up incompatible internal-using functions in an
* aggregate.
*/
- transTypeId = typenameTypeId(NULL, transType);
- transTypeType = get_typtype(transTypeId);
- if (transTypeType == TYPTYPE_PSEUDO &&
- !IsPolymorphicType(transTypeId))
+ if (transType)
{
- if (transTypeId == INTERNALOID && superuser())
- /* okay */ ;
- else
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("aggregate transition data type cannot be %s",
- format_type_be(transTypeId))));
+ transTypeId = typenameTypeId(NULL, transType);
+ transTypeType = get_typtype(transTypeId);
+ if (transTypeType == TYPTYPE_PSEUDO &&
+ !IsPolymorphicType(transTypeId))
+ {
+ if (transTypeId != INTERNALOID || !superuser() || isOrderedSet)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("aggregate transition data type cannot be %s",
+ format_type_be(transTypeId))));
+ }
+
}
/*
@@ -224,13 +273,23 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
* value. However, if it's an incorrect value it seems much more
* user-friendly to complain at CREATE AGGREGATE time.
*/
- if (initval && transTypeType != TYPTYPE_PSEUDO)
+ if (transType)
{
- Oid typinput,
- typioparam;
+ if (initval && transTypeType != TYPTYPE_PSEUDO)
+ {
+ Oid typinput,
+ typioparam;
- getTypeInputInfo(transTypeId, &typinput, &typioparam);
- (void) OidInputFunctionCall(typinput, initval, typioparam, -1);
+ getTypeInputInfo(transTypeId, &typinput, &typioparam);
+ (void) OidInputFunctionCall(typinput, initval, typioparam, -1);
+ }
+ }
+ else
+ {
+ if (initval)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("INITVAL must not be specified without STYPE")));
}
/*
@@ -239,6 +298,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
return AggregateCreate(aggName, /* aggregate name */
aggNamespace, /* namespace */
numArgs,
+ numDirectArgs,
parameterTypes,
PointerGetDatum(allParameterTypes),
PointerGetDatum(parameterModes),
@@ -247,6 +307,10 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
transfuncName, /* step function name */
finalfuncName, /* final function name */
sortoperatorName, /* sort operator name */
+ transsortoperatorName, /* transsort operator name */
transTypeId, /* transition data type */
- initval); /* initial condition */
+ initval, /* initial condition */
+ isStrict, /* is explicitly STRICT */
+ isOrderedSet, /* If the function is an ordered set */
+ ishypothetical); /* If the function is a hypothetical set */
}
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index ca754b4..2399446 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -274,8 +274,13 @@ interpret_function_parameter_list(List *parameters,
/* handle input parameters */
if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
{
- /* other input parameters can't follow a VARIADIC parameter */
- if (varCount > 0)
+ /*
+ * For functions, other input parameters can't follow a VARIADIC
+ * parameter; for aggregates, we might be dealing with an ordered
+ * set function which have more complex rules for variadics, so
+ * punt the error checking for that case to the caller.
+ */
+ if (varCount > 0 && !is_aggregate)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("VARIADIC parameter must be the last input parameter")));
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 90c2753..e6fb8b0 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -4410,6 +4410,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
parent);
+ astate->orddirectargs = (List *) ExecInitExpr((Expr *) aggref->orddirectargs, parent);
astate->aggfilter = ExecInitExpr(aggref->aggfilter,
parent);
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index ff6a123..f216cd4 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -380,8 +380,8 @@ sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
param = ParseFuncOrColumn(pstate,
list_make1(subfield),
list_make1(param),
- NIL, NULL, false, false, false,
- NULL, true, cref->location);
+ cref->location,
+ NULL);
}
return param;
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index e02a6ff..3f2cf8d 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -3,7 +3,7 @@
* nodeAgg.c
* Routines to handle aggregate nodes.
*
- * ExecAgg evaluates each aggregate in the following steps:
+ * ExecAgg evaluates each normal aggregate in the following steps:
*
* transvalue = initcond
* foreach input_tuple do
@@ -66,6 +66,26 @@
* AggState is available as context in earlier releases (back to 8.1),
* but direct examination of the node is needed to use it before 9.0.
*
+ *---
+ *
+ * Ordered set functions modify the above process in a number of ways.
+ * Most importantly, they do not have transfuncs at all; the same sort
+ * mechanism used for ORDER BY/DISTINCT as described above is used to
+ * process the input, but then the finalfunc is called without actually
+ * running the sort (the finalfunc is allowed to insert rows first).
+ * The finalfunc has access via a set of AggSet* API functions to the
+ * Tuplesortstate, row count in the group, and other ancillary info.
+ *
+ * Ordered set functions can, however, have a transvalue declared; this is
+ * treated as a constant, and added to the end of the sort fields.
+ * Hypothetical set functions use this to provide a flag that distinguishes
+ * the hypothetical row from the input data.
+ *
+ * Since they have no transfunc, ordered set functions have their own
+ * 'strict' flag stored in the aggregate's own pg_proc entry; this affects
+ * whether rows containing nulls are placed in the sorter. But since we
+ * pass dummy null arguments to the finalfunc for type resolution purposes,
+ * no ordered set finalfunc is allowed to be strict.
*
* Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -87,10 +107,12 @@
#include "executor/nodeAgg.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+#include "nodes/makefuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/tlist.h"
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
+#include "parser/parse_clause.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
@@ -105,6 +127,8 @@
*/
typedef struct AggStatePerAggData
{
+ NodeTag type;
+
/*
* These values are set up during ExecInitAgg() and do not change
* thereafter:
@@ -114,10 +138,25 @@ typedef struct AggStatePerAggData
AggrefExprState *aggrefstate;
Aggref *aggref;
- /* number of input arguments for aggregate function proper */
+ /* Pointer to parent AggState node */
+ AggState *aggstate;
+
+ /* copied from aggref */
+ bool isOrderedSet;
+
+ /*
+ * number of arguments for aggregate function proper.
+ * For ordered set functions, this includes the ORDER BY
+ * columns, *except* in the case of hypothetical set functions.
+ */
int numArguments;
- /* number of inputs including ORDER BY expressions */
+ /*
+ * number of inputs including ORDER BY expressions. For ordered
+ * set functions, *only* the ORDER BY expressions are included
+ * here, since the direct args to the function are not properly
+ * "input" in the sense of being derived from the tuple group.
+ */
int numInputs;
/* Oids of transfer functions */
@@ -126,12 +165,23 @@ typedef struct AggStatePerAggData
/*
* fmgr lookup data for transfer functions --- only valid when
- * corresponding oid is not InvalidOid. Note in particular that fn_strict
- * flags are kept here.
+ * corresponding oid is not InvalidOid.
*/
FmgrInfo transfn;
FmgrInfo finalfn;
+ /*
+ * If >0, aggregate as a whole is strict (skips null input)
+ * The value specifies how many columns to check; normal aggs
+ * only check numArguments, while ordered set functions check
+ * numInputs.
+ *
+ * Ordered set functions are not allowed to have strict finalfns;
+ * other aggregates respect the finalfn strict flag in the
+ * FmgrInfo above.
+ */
+ int numStrict;
+
/* Input collation derived for aggregate */
Oid aggCollation;
@@ -148,6 +198,9 @@ typedef struct AggStatePerAggData
Oid *sortCollations;
bool *sortNullsFirst;
+ /* just for convenience of ordered set funcs, not used here */
+ Oid *sortEqOperators;
+
/*
* fmgr lookup data for input columns' equality operators --- only
* set/used when aggregate has DISTINCT flag. Note that these are in
@@ -204,6 +257,9 @@ typedef struct AggStatePerAggData
*/
Tuplesortstate *sortstate; /* sort object, if DISTINCT or ORDER BY */
+
+ int64 number_of_rows; /* number of rows */
+
} AggStatePerAggData;
/*
@@ -300,6 +356,8 @@ initialize_aggregates(AggState *aggstate,
AggStatePerAgg peraggstate = &peragg[aggno];
AggStatePerGroup pergroupstate = &pergroup[aggno];
+ peraggstate->number_of_rows = 0;
+
/*
* Start a fresh sort operation for each DISTINCT/ORDER BY aggregate.
*/
@@ -383,14 +441,17 @@ advance_transition_function(AggState *aggstate,
MemoryContext oldContext;
Datum newVal;
int i;
+ int numStrict = peraggstate->numStrict;
- if (peraggstate->transfn.fn_strict)
+ Assert(OidIsValid(peraggstate->transfn_oid));
+
+ if (numStrict > 0)
{
/*
* For a strict transfn, nothing happens when there's a NULL input; we
* just keep the prior transValue.
*/
- for (i = 1; i <= numArguments; i++)
+ for (i = 1; i <= numStrict; i++)
{
if (fcinfo->argnull[i])
return;
@@ -506,24 +567,24 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
if (peraggstate->numSortCols > 0)
{
+ int numStrict = peraggstate->numStrict;
+
/* DISTINCT and/or ORDER BY case */
Assert(slot->tts_nvalid == peraggstate->numInputs);
/*
- * If the transfn is strict, we want to check for nullity before
+ * If the aggregate is strict, we want to check for nullity before
* storing the row in the sorter, to save space if there are a lot
- * of nulls. Note that we must only check numArguments columns,
- * not numInputs, since nullity in columns used only for sorting
- * is not relevant here.
+ * of nulls.
*/
- if (peraggstate->transfn.fn_strict)
+ if (numStrict > 0)
{
- for (i = 0; i < nargs; i++)
+ for (i = 0; i < numStrict; i++)
{
if (slot->tts_isnull[i])
break;
}
- if (i < nargs)
+ if (i < numStrict)
continue;
}
@@ -534,6 +595,8 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
slot->tts_isnull[0]);
else
tuplesort_puttupleslot(peraggstate->sortstate, slot);
+
+ peraggstate->number_of_rows++;
}
else
{
@@ -756,15 +819,66 @@ finalize_aggregate(AggState *aggstate,
if (OidIsValid(peraggstate->finalfn_oid))
{
FunctionCallInfoData fcinfo;
+ bool isnull = false;
+
+ if (!(peraggstate->isOrderedSet))
+ {
+ InitFunctionCallInfoData(fcinfo,
+ &(peraggstate->finalfn),
+ 1,
+ peraggstate->aggCollation,
+ (void *) aggstate,
+ NULL);
+
+ fcinfo.arg[0] = pergroupstate->transValue;
+ fcinfo.argnull[0] = isnull = pergroupstate->transValueIsNull;
+ }
+ else
+ {
+ List *args = peraggstate->aggrefstate->orddirectargs;
+ ListCell *lc;
+ int i = 0;
+ int numArguments = peraggstate->numArguments;
+
+ ExecClearTuple(peraggstate->evalslot);
+ ExecClearTuple(peraggstate->uniqslot);
+
+ InitFunctionCallInfoData(fcinfo,
+ &(peraggstate->finalfn),
+ peraggstate->numArguments,
+ peraggstate->aggCollation,
+ (void *) peraggstate,
+ NULL);
+
+ foreach (lc, args)
+ {
+ ExprState *expr = (ExprState *) lfirst(lc);
+
+ fcinfo.arg[i] = ExecEvalExpr(expr,
+ aggstate->ss.ps.ps_ExprContext,
+ &fcinfo.argnull[i],
+ NULL);
+ if (fcinfo.argnull[i])
+ isnull = true;
- InitFunctionCallInfoData(fcinfo, &(peraggstate->finalfn), 1,
- peraggstate->aggCollation,
- (void *) aggstate, NULL);
- fcinfo.arg[0] = pergroupstate->transValue;
- fcinfo.argnull[0] = pergroupstate->transValueIsNull;
- if (fcinfo.flinfo->fn_strict && pergroupstate->transValueIsNull)
+ ++i;
+ }
+
+ for(; i < numArguments; i++)
+ {
+ fcinfo.arg[i] = (Datum) 0;
+ fcinfo.argnull[i] = true;
+ isnull = true;
+ }
+ }
+
+ if (isnull && fcinfo.flinfo->fn_strict)
{
- /* don't call a strict function with NULL inputs */
+ /*
+ * don't call a strict function with NULL inputs; for ordered set
+ * functions this is paranoia, we already required that fn_strict
+ * is false, but easy to check anyway
+ */
*resultVal = (Datum) 0;
*resultIsNull = true;
}
@@ -1164,6 +1278,17 @@ agg_retrieve_direct(AggState *aggstate)
}
/*
+ * Use the representative input tuple for any references to
+ * non-aggregated input columns in the qual and tlist. (If we are not
+ * grouping, and there are no input rows at all, we will come here
+ * with an empty firstSlot ... but if not grouping, there can't be any
+ * references to non-aggregated input columns, so no problem.)
+ * We do this before finalizing because for ordered set functions,
+ * finalize_aggregates can evaluate arguments referencing the tuple.
+ */
+ econtext->ecxt_outertuple = firstSlot;
+
+ /*
* Done scanning input tuple group. Finalize each aggregate
* calculation, and stash results in the per-output-tuple context.
*/
@@ -1174,14 +1299,17 @@ agg_retrieve_direct(AggState *aggstate)
if (peraggstate->numSortCols > 0)
{
- if (peraggstate->numInputs == 1)
- process_ordered_aggregate_single(aggstate,
- peraggstate,
- pergroupstate);
- else
- process_ordered_aggregate_multi(aggstate,
- peraggstate,
- pergroupstate);
+ if (!(peraggstate->isOrderedSet))
+ {
+ if (peraggstate->numInputs == 1)
+ process_ordered_aggregate_single(aggstate,
+ peraggstate,
+ pergroupstate);
+ else
+ process_ordered_aggregate_multi(aggstate,
+ peraggstate,
+ pergroupstate);
+ }
}
finalize_aggregate(aggstate, peraggstate, pergroupstate,
@@ -1189,15 +1317,6 @@ agg_retrieve_direct(AggState *aggstate)
}
/*
- * Use the representative input tuple for any references to
- * non-aggregated input columns in the qual and tlist. (If we are not
- * grouping, and there are no input rows at all, we will come here
- * with an empty firstSlot ... but if not grouping, there can't be any
- * references to non-aggregated input columns, so no problem.)
- */
- econtext->ecxt_outertuple = firstSlot;
-
- /*
* Check the qual (HAVING clause); if the group does not match, ignore
* it and loop back to try to process another group.
*/
@@ -1568,10 +1687,12 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
int numInputs;
int numSortCols;
int numDistinctCols;
+ bool isOrderedSet = aggref->isordset;
List *sortlist;
HeapTuple aggTuple;
Form_pg_aggregate aggform;
Oid aggtranstype;
+ Oid aggtranstypecoll;
AclResult aclresult;
Oid transfn_oid,
finalfn_oid;
@@ -1580,6 +1701,10 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
Datum textInitVal;
int i;
ListCell *lc;
+ bool is_strict;
+ Oid inputCollations[FUNC_MAX_ARGS];
+ List *argexprs;
+ List *argexprstate;
/* Planner should have assigned aggregate to correct level */
Assert(aggref->agglevelsup == 0);
@@ -1601,31 +1726,18 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
/* Nope, so assign a new PerAgg record */
peraggstate = &peragg[++aggno];
- /* Mark Aggref state node with assigned index in the result array */
- aggrefstate->aggno = aggno;
-
/* Fill in the peraggstate data */
- peraggstate->aggrefstate = aggrefstate;
+ peraggstate->type = T_AggStatePerAggData;
+ peraggstate->aggstate = aggstate;
peraggstate->aggref = aggref;
- numInputs = list_length(aggref->args);
- peraggstate->numInputs = numInputs;
- peraggstate->sortstate = NULL;
+ peraggstate->aggrefstate = aggrefstate;
- /*
- * Get actual datatypes of the inputs. These could be different from
- * the agg's declared input types, when the agg accepts ANY or a
- * polymorphic type.
- */
- numArguments = 0;
- foreach(lc, aggref->args)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ peraggstate->isOrderedSet = isOrderedSet;
- if (!tle->resjunk)
- inputTypes[numArguments++] = exprType((Node *) tle->expr);
- }
- peraggstate->numArguments = numArguments;
+ /* Mark Aggref state node with assigned index in the result array */
+ aggrefstate->aggno = aggno;
+ /* Fetch the pg_aggregate row */
aggTuple = SearchSysCache1(AGGFNOID,
ObjectIdGetDatum(aggref->aggfnoid));
if (!HeapTupleIsValid(aggTuple))
@@ -1633,6 +1745,13 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
aggref->aggfnoid);
aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
+ /*
+ * Check that the definition hasn't somehow changed incompatibly.
+ */
+ if (isOrderedSet != (aggform->aggisordsetfunc)
+ || (aggref->ishypothetical != (aggform->aggordnargs == -2)))
+ elog(ERROR, "incompatible change to aggregate definition");
+
/* Check permission to call aggregate function */
aclresult = pg_proc_aclcheck(aggref->aggfnoid, GetUserId(),
ACL_EXECUTE);
@@ -1644,25 +1763,37 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
- /* Check that aggregate owner has permission to call component fns */
+ /*
+ * Check that aggregate owner has permission to call component fns
+ * In passing, fetch the proisstrict flag for the aggregate proper,
+ * which subs for the transfn's strictness flag in cases where there
+ * is no transfn.
+ */
{
HeapTuple procTuple;
Oid aggOwner;
+ Form_pg_proc procp;
procTuple = SearchSysCache1(PROCOID,
ObjectIdGetDatum(aggref->aggfnoid));
if (!HeapTupleIsValid(procTuple))
elog(ERROR, "cache lookup failed for function %u",
aggref->aggfnoid);
- aggOwner = ((Form_pg_proc) GETSTRUCT(procTuple))->proowner;
+ procp = (Form_pg_proc) GETSTRUCT(procTuple);
+ aggOwner = procp->proowner;
+ is_strict = procp->proisstrict;
ReleaseSysCache(procTuple);
- aclresult = pg_proc_aclcheck(transfn_oid, aggOwner,
- ACL_EXECUTE);
- if (aclresult != ACLCHECK_OK)
+ if (OidIsValid(transfn_oid))
+ {
+ aclresult = pg_proc_aclcheck(transfn_oid, aggOwner,
+ ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK && OidIsValid(transfn_oid))
aclcheck_error(aclresult, ACL_KIND_PROC,
get_func_name(transfn_oid));
- InvokeFunctionExecuteHook(transfn_oid);
+ InvokeFunctionExecuteHook(transfn_oid);
+ }
+
if (OidIsValid(finalfn_oid))
{
aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
@@ -1674,17 +1805,37 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
}
}
+ /*
+ * Get actual datatypes of the inputs. These could be different from
+ * the agg's declared input types, when the agg accepts ANY or a
+ * polymorphic type.
+ */
+
+ peraggstate->numInputs = numInputs = list_length(aggref->args);
+
+ numArguments = get_aggregate_argtypes(aggref,
+ inputTypes,
+ inputCollations);
+
/* resolve actual type of transition state, if polymorphic */
aggtranstype = aggform->aggtranstype;
- if (IsPolymorphicType(aggtranstype))
+ if (OidIsValid(aggtranstype) && IsPolymorphicType(aggtranstype))
{
/* have to fetch the agg's declared input types... */
Oid *declaredArgTypes;
- int agg_nargs;
+ int agg_nargs;
(void) get_func_signature(aggref->aggfnoid,
- &declaredArgTypes, &agg_nargs);
- Assert(agg_nargs == numArguments);
+ &declaredArgTypes,
+ &agg_nargs);
+
+ /*
+ * if variadic "any", might be more actual args than declared
+ * args, but these extra args can't influence the determination
+ * of polymorphic transition or result type.
+ */
+ Assert(agg_nargs <= numArguments);
+
aggtranstype = enforce_generic_type_consistency(inputTypes,
declaredArgTypes,
agg_nargs,
@@ -1693,35 +1844,82 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
pfree(declaredArgTypes);
}
+ aggtranstypecoll = get_typcollation(aggtranstype);
+
/* build expression trees using actual argument & result types */
- build_aggregate_fnexprs(inputTypes,
- numArguments,
- aggref->aggvariadic,
- aggtranstype,
- aggref->aggtype,
- aggref->inputcollid,
- transfn_oid,
- finalfn_oid,
- &transfnexpr,
- &finalfnexpr);
-
- fmgr_info(transfn_oid, &peraggstate->transfn);
- fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
+
+ if (!isOrderedSet)
+ {
+ build_aggregate_fnexprs(inputTypes,
+ numArguments,
+ aggref->aggvariadic,
+ aggtranstype,
+ aggref->aggtype,
+ aggref->inputcollid,
+ transfn_oid,
+ finalfn_oid,
+ &transfnexpr,
+ &finalfnexpr);
+ }
+ else
+ {
+ /*
+ * The transvalue counts as an argument, but not for hypothetical
+ * set funcs.
+ */
+ if (OidIsValid(aggtranstype) && !(aggref->ishypothetical))
+ {
+ if (numArguments == FUNC_MAX_ARGS)
+ elog(ERROR, "too many arguments to ordered set function");
+
+ inputTypes[numArguments++] = aggtranstype;
+ inputCollations[numArguments++] = aggtranstypecoll;
+ }
+
+ build_orderedset_fnexprs(inputTypes,
+ numArguments,
+ aggref->aggvariadic,
+ aggref->aggtype,
+ aggref->inputcollid,
+ inputCollations,
+ finalfn_oid,
+ &finalfnexpr);
+ }
+
+ peraggstate->numArguments = numArguments;
+
+ if (OidIsValid(transfn_oid))
+ {
+ fmgr_info(transfn_oid, &peraggstate->transfn);
+ fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
+
+ is_strict = peraggstate->transfn.fn_strict;
+ }
if (OidIsValid(finalfn_oid))
{
fmgr_info(finalfn_oid, &peraggstate->finalfn);
fmgr_info_set_expr((Node *) finalfnexpr, &peraggstate->finalfn);
+ if (peraggstate->finalfn.fn_strict && isOrderedSet)
+ elog(ERROR, "ordered set finalfns must not be strict");
}
+ if (is_strict)
+ peraggstate->numStrict = (isOrderedSet ? numInputs : numArguments);
+ else
+ peraggstate->numStrict = 0;
+
peraggstate->aggCollation = aggref->inputcollid;
get_typlenbyval(aggref->aggtype,
&peraggstate->resulttypeLen,
&peraggstate->resulttypeByVal);
- get_typlenbyval(aggtranstype,
- &peraggstate->transtypeLen,
- &peraggstate->transtypeByVal);
+ if (OidIsValid(aggtranstype))
+ {
+ get_typlenbyval(aggtranstype,
+ &peraggstate->transtypeLen,
+ &peraggstate->transtypeByVal);
+ }
/*
* initval is potentially null, so don't try to access it as a struct
@@ -1744,7 +1942,9 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
* transValue. This should have been checked at agg definition time,
* but just in case...
*/
- if (peraggstate->transfn.fn_strict && peraggstate->initValueIsNull)
+ if (OidIsValid(peraggstate->transfn_oid)
+ && peraggstate->transfn.fn_strict
+ && peraggstate->initValueIsNull)
{
if (numArguments < 1 ||
!IsBinaryCoercible(inputTypes[0], aggtranstype))
@@ -1754,21 +1954,8 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
aggref->aggfnoid)));
}
- /*
- * Get a tupledesc corresponding to the inputs (including sort
- * expressions) of the agg.
- */
- peraggstate->evaldesc = ExecTypeFromTL(aggref->args, false);
-
- /* Create slot we're going to do argument evaluation in */
- peraggstate->evalslot = ExecInitExtraTupleSlot(estate);
- ExecSetSlotDescriptor(peraggstate->evalslot, peraggstate->evaldesc);
-
- /* Set up projection info for evaluation */
- peraggstate->evalproj = ExecBuildProjectionInfo(aggrefstate->args,
- aggstate->tmpcontext,
- peraggstate->evalslot,
- NULL);
+ argexprs = aggref->args;
+ argexprstate = aggrefstate->args;
/*
* If we're doing either DISTINCT or ORDER BY, then we have a list of
@@ -1777,6 +1964,11 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
*
* Note that by construction, if there is a DISTINCT clause then the
* ORDER BY clause is a prefix of it (see transformDistinctClause).
+ *
+ * If we're doing an ordered set function, though, we want to do the
+ * initialization for DISTINCT since the ordered set finalfn might
+ * want it, and it's much easier to do it here. So set numDistinctCols
+ * and let the later initialization take care of it.
*/
if (aggref->aggdistinct)
{
@@ -1788,11 +1980,86 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
{
sortlist = aggref->aggorder;
numSortCols = list_length(sortlist);
- numDistinctCols = 0;
+ numDistinctCols = isOrderedSet ? numSortCols : 0;
+ }
+
+ /*
+ * If this is an ordered set function, and we have a transtype, then
+ * it represents an extra column to be added to the sorter with a
+ * fixed value. Plus, if aggtranssortop is valid, we have to include
+ * a sort entry for the new column.
+ *
+ * I'd probably have done this in the planner if I'd seen any
+ * possible place to put it; if there is one, it's very obscure.
+ */
+
+ if (OidIsValid(aggtranstype) && isOrderedSet)
+ {
+ Oid sortop = aggform->aggtranssortop;
+ Const *node = makeNode(Const);
+ TargetEntry *tle;
+ SortGroupClause *sortcl = NULL;
+
+ node->consttype = aggtranstype;
+ node->consttypmod = -1;
+ node->constcollid = aggtranstypecoll;
+ node->constlen = peraggstate->transtypeLen;
+ node->constvalue = peraggstate->initValue;
+ node->constisnull = peraggstate->initValueIsNull;
+ node->constbyval = peraggstate->transtypeByVal;
+ node->location = -1;
+
+ tle = makeTargetEntry((Expr *) node,
+ ++numInputs,
+ NULL,
+ true);
+
+ peraggstate->numInputs = numInputs;
+
+ if (OidIsValid(sortop))
+ {
+ Assert(aggref->aggdistinct == NIL);
+
+ sortcl = makeNode(SortGroupClause);
+
+ sortcl->tleSortGroupRef = assignSortGroupRef(tle, argexprs);
+
+ sortcl->sortop = sortop;
+ sortcl->hashable = false;
+ sortcl->eqop = get_equality_op_for_ordering_op(sortop,
+ &sortcl->nulls_first);
+
+ sortlist = lappend(list_copy(sortlist), sortcl);
+ ++numSortCols;
+ ++numDistinctCols;
+ }
+
+ /* shallow-copy the passed-in lists, which we must not scribble on. */
+
+ argexprs = lappend(list_copy(argexprs), (Node *) tle);
+ argexprstate = lappend(list_copy(argexprstate),
+ ExecInitExpr((Expr *) tle, (PlanState *) aggstate));
}
peraggstate->numSortCols = numSortCols;
peraggstate->numDistinctCols = numDistinctCols;
+ peraggstate->sortstate = NULL;
+
+ /*
+ * Get a tupledesc corresponding to the inputs (including sort
+ * expressions) of the agg.
+ */
+ peraggstate->evaldesc = ExecTypeFromTL(argexprs, false);
+
+ /* Create slot we're going to do argument evaluation in */
+ peraggstate->evalslot = ExecInitExtraTupleSlot(estate);
+ ExecSetSlotDescriptor(peraggstate->evalslot, peraggstate->evaldesc);
+
+ /* Set up projection info for evaluation */
+ peraggstate->evalproj = ExecBuildProjectionInfo(argexprstate,
+ aggstate->tmpcontext,
+ peraggstate->evalslot,
+ NULL);
if (numSortCols > 0)
{
@@ -1805,11 +2072,12 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
/* If we have only one input, we need its len/byval info. */
if (numInputs == 1)
{
- get_typlenbyval(inputTypes[0],
+ get_typlenbyval(peraggstate->evaldesc->attrs[0]->atttypid,
&peraggstate->inputtypeLen,
&peraggstate->inputtypeByVal);
}
- else if (numDistinctCols > 0)
+
+ if (numDistinctCols > 0 && (numInputs > 1 || isOrderedSet))
{
/* we will need an extra slot to store prior values */
peraggstate->uniqslot = ExecInitExtraTupleSlot(estate);
@@ -1822,50 +2090,47 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
(AttrNumber *) palloc(numSortCols * sizeof(AttrNumber));
peraggstate->sortOperators =
(Oid *) palloc(numSortCols * sizeof(Oid));
+ peraggstate->sortEqOperators =
+ (Oid *) palloc(numSortCols * sizeof(Oid));
peraggstate->sortCollations =
(Oid *) palloc(numSortCols * sizeof(Oid));
peraggstate->sortNullsFirst =
(bool *) palloc(numSortCols * sizeof(bool));
+ if (numDistinctCols > 0)
+ peraggstate->equalfns =
+ (FmgrInfo *) palloc(numDistinctCols * sizeof(FmgrInfo));
+ else
+ peraggstate->equalfns = NULL;
+
i = 0;
foreach(lc, sortlist)
{
SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
TargetEntry *tle = get_sortgroupclause_tle(sortcl,
- aggref->args);
+ argexprs);
/* the parser should have made sure of this */
Assert(OidIsValid(sortcl->sortop));
peraggstate->sortColIdx[i] = tle->resno;
peraggstate->sortOperators[i] = sortcl->sortop;
+ peraggstate->sortEqOperators[i] = sortcl->eqop;
peraggstate->sortCollations[i] = exprCollation((Node *) tle->expr);
peraggstate->sortNullsFirst[i] = sortcl->nulls_first;
- i++;
- }
- Assert(i == numSortCols);
- }
- if (aggref->aggdistinct)
- {
- Assert(numArguments > 0);
-
- /*
- * We need the equal function for each DISTINCT comparison we will
- * make.
- */
- peraggstate->equalfns =
- (FmgrInfo *) palloc(numDistinctCols * sizeof(FmgrInfo));
-
- i = 0;
- foreach(lc, aggref->aggdistinct)
- {
- SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
+ /*
+ * It's OK to get the equalfns here too, since we already
+ * require that sortlist is aggref->aggdistinct for the normal
+ * distinct case, and for ordered set functions using the
+ * (possibly modified copy of) aggref->aggorder is correct
+ */
+ if (peraggstate->equalfns)
+ fmgr_info(get_opcode(sortcl->eqop), &peraggstate->equalfns[i]);
- fmgr_info(get_opcode(sortcl->eqop), &peraggstate->equalfns[i]);
i++;
}
- Assert(i == numDistinctCols);
+ Assert(i == numSortCols);
}
ReleaseSysCache(aggTuple);
@@ -2023,6 +2288,9 @@ ExecReScanAgg(AggState *node)
* If aggcontext isn't NULL, the function also stores at *aggcontext the
* identity of the memory context that aggregate transition values are
* being stored in.
+ *
+ * We do NOT include AGG_CONTEXT_ORDERED as a possible return here, since
+ * that would open a security hole.
*/
int
AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
@@ -2063,3 +2331,118 @@ aggregate_dummy(PG_FUNCTION_ARGS)
fcinfo->flinfo->fn_oid);
return (Datum) 0; /* keep compiler quiet */
}
+
+/* AggSetGetRowCount - Get the number of rows in case of ordered set
+ * functions.
+ */
+int64
+AggSetGetRowCount(FunctionCallInfo fcinfo)
+{
+ if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ {
+ return ((AggStatePerAggData *)fcinfo->context)->number_of_rows;
+ }
+
+ elog(ERROR, "Called AggSetGetRowCount on non ordered set function");
+ return -1;
+}
+
+/* AggSetGetSortInfo - Get the sort state in the case of
+ * ordered set functions.
+ */
+void
+AggSetGetSortInfo(FunctionCallInfo fcinfo,
+ Tuplesortstate **sortstate,
+ TupleDesc *tupdesc,
+ TupleTableSlot **tupslot,
+ Oid *datumtype)
+{
+ if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ {
+ AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+
+ *sortstate = peraggstate->sortstate;
+ if (peraggstate->numInputs == 1)
+ {
+ if (tupdesc)
+ *tupdesc = NULL;
+ if (datumtype)
+ *datumtype = peraggstate->evaldesc->attrs[0]->atttypid;
+ }
+ else
+ {
+ if (tupdesc)
+ *tupdesc = peraggstate->evaldesc;
+ if (datumtype)
+ *datumtype = InvalidOid;
+ }
+
+ if (tupslot)
+ *tupslot = peraggstate->evalslot;
+ }
+ else
+ elog(ERROR, "AggSetSortInfo called on non ordered set function");
+}
+
+int
+AggSetGetDistinctInfo(FunctionCallInfo fcinfo,
+ TupleTableSlot **uniqslot,
+ AttrNumber **sortColIdx,
+ FmgrInfo **equalfns)
+{
+ if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ {
+ AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+
+ if (uniqslot)
+ *uniqslot = peraggstate->uniqslot;
+ if (sortColIdx)
+ *sortColIdx = peraggstate->sortColIdx;
+ if (equalfns)
+ *equalfns = peraggstate->equalfns;
+
+ return peraggstate->numDistinctCols;
+ }
+ else
+ elog(ERROR, "AggSetGetDistinctOperators called on non ordered set function");
+}
+
+int
+AggSetGetSortOperators(FunctionCallInfo fcinfo,
+ AttrNumber **sortColIdx,
+ Oid **sortOperators,
+ Oid **sortEqOperators,
+ Oid **sortCollations,
+ bool **sortNullsFirst)
+{
+ if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ {
+ AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+
+ if (sortColIdx)
+ *sortColIdx = peraggstate->sortColIdx;
+ if (sortOperators)
+ *sortOperators = peraggstate->sortOperators;
+ if (sortEqOperators)
+ *sortEqOperators = peraggstate->sortEqOperators;
+ if (sortCollations)
+ *sortCollations = peraggstate->sortCollations;
+ if (sortNullsFirst)
+ *sortNullsFirst = peraggstate->sortNullsFirst;
+
+ return peraggstate->numSortCols;
+ }
+ else
+ elog(ERROR, "AggSetGetSortOperators called on non ordered set function");
+}
+
+void
+AggSetGetPerTupleContext(FunctionCallInfo fcinfo,
+ MemoryContext *memcontext)
+{
+ if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ {
+ AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+ *memcontext = peraggstate->aggstate->tmpcontext->ecxt_per_tuple_memory;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 65f3b98..12396a5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1139,9 +1139,12 @@ _copyAggref(const Aggref *from)
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(aggorder);
COPY_NODE_FIELD(aggdistinct);
+ COPY_NODE_FIELD(orddirectargs);
COPY_NODE_FIELD(aggfilter);
COPY_SCALAR_FIELD(aggstar);
COPY_SCALAR_FIELD(aggvariadic);
+ COPY_SCALAR_FIELD(isordset);
+ COPY_SCALAR_FIELD(ishypothetical);
COPY_SCALAR_FIELD(agglevelsup);
COPY_LOCATION_FIELD(location);
@@ -2174,6 +2177,7 @@ _copyFuncCall(const FuncCall *from)
COPY_SCALAR_FIELD(agg_star);
COPY_SCALAR_FIELD(agg_distinct);
COPY_SCALAR_FIELD(func_variadic);
+ COPY_SCALAR_FIELD(has_within_group);
COPY_NODE_FIELD(over);
COPY_LOCATION_FIELD(location);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4c9b05e..537a5e4 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -196,9 +196,12 @@ _equalAggref(const Aggref *a, const Aggref *b)
COMPARE_NODE_FIELD(args);
COMPARE_NODE_FIELD(aggorder);
COMPARE_NODE_FIELD(aggdistinct);
+ COMPARE_NODE_FIELD(orddirectargs);
COMPARE_NODE_FIELD(aggfilter);
COMPARE_SCALAR_FIELD(aggstar);
COMPARE_SCALAR_FIELD(aggvariadic);
+ COMPARE_SCALAR_FIELD(isordset);
+ COMPARE_SCALAR_FIELD(ishypothetical);
COMPARE_SCALAR_FIELD(agglevelsup);
COMPARE_LOCATION_FIELD(location);
@@ -2006,6 +2009,7 @@ _equalFuncCall(const FuncCall *a, const FuncCall *b)
COMPARE_SCALAR_FIELD(agg_star);
COMPARE_SCALAR_FIELD(agg_distinct);
COMPARE_SCALAR_FIELD(func_variadic);
+ COMPARE_SCALAR_FIELD(has_within_group);
COMPARE_NODE_FIELD(over);
COMPARE_LOCATION_FIELD(location);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index b742ec9..5be82ba 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -558,6 +558,7 @@ makeFuncCall(List *name, List *args, int location)
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->has_within_group = FALSE;
n->over = NULL;
return n;
}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 908f397..e84b371 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1631,6 +1631,11 @@ expression_tree_walker(Node *node,
if (expression_tree_walker((Node *) expr->aggdistinct,
walker, context))
return true;
+
+ if (expression_tree_walker((Node *) expr->orddirectargs,
+ walker, context))
+ return true;
+
if (walker((Node *) expr->aggfilter, context))
return true;
}
@@ -2155,7 +2160,9 @@ expression_tree_mutator(Node *node,
MUTATE(newnode->args, aggref->args, List *);
MUTATE(newnode->aggorder, aggref->aggorder, List *);
MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
+ MUTATE(newnode->orddirectargs, aggref->orddirectargs, List *);
MUTATE(newnode->aggfilter, aggref->aggfilter, Expr *);
+
return (Node *) newnode;
}
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 817b149..8f6e293 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -960,9 +960,12 @@ _outAggref(StringInfo str, const Aggref *node)
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(aggorder);
WRITE_NODE_FIELD(aggdistinct);
+ WRITE_NODE_FIELD(orddirectargs);
WRITE_NODE_FIELD(aggfilter);
WRITE_BOOL_FIELD(aggstar);
WRITE_BOOL_FIELD(aggvariadic);
+ WRITE_BOOL_FIELD(isordset);
+ WRITE_BOOL_FIELD(ishypothetical);
WRITE_UINT_FIELD(agglevelsup);
WRITE_LOCATION_FIELD(location);
}
@@ -2090,6 +2093,7 @@ _outFuncCall(StringInfo str, const FuncCall *node)
WRITE_BOOL_FIELD(agg_star);
WRITE_BOOL_FIELD(agg_distinct);
WRITE_BOOL_FIELD(func_variadic);
+ WRITE_BOOL_FIELD(has_within_group);
WRITE_NODE_FIELD(over);
WRITE_LOCATION_FIELD(location);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index d325bb3..950645d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -495,9 +495,12 @@ _readAggref(void)
READ_NODE_FIELD(args);
READ_NODE_FIELD(aggorder);
READ_NODE_FIELD(aggdistinct);
+ READ_NODE_FIELD(orddirectargs);
READ_NODE_FIELD(aggfilter);
READ_BOOL_FIELD(aggstar);
READ_BOOL_FIELD(aggvariadic);
+ READ_BOOL_FIELD(isordset);
+ READ_BOOL_FIELD(ishypothetical);
READ_UINT_FIELD(agglevelsup);
READ_LOCATION_FIELD(location);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 76c032c..6820e82 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -39,6 +39,7 @@
#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
+#include "parser/parse_agg.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -464,7 +465,6 @@ count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
QualCost argcosts;
Oid *inputTypes;
int numArguments;
- ListCell *l;
Assert(aggref->agglevelsup == 0);
@@ -486,7 +486,8 @@ count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
costs->numOrderedAggs++;
/* add component function execution costs to appropriate totals */
- costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost;
+ if (OidIsValid(aggtransfn))
+ costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost;
if (OidIsValid(aggfinalfn))
costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost;
@@ -504,72 +505,91 @@ count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
costs->transCost.startup += argcosts.startup;
costs->transCost.per_tuple += argcosts.per_tuple;
- /* extract argument types (ignoring any ORDER BY expressions) */
- inputTypes = (Oid *) palloc(sizeof(Oid) * list_length(aggref->args));
- numArguments = 0;
- foreach(l, aggref->args)
+ /*
+ * If we're doing a sorted agg, we can punt the entire
+ * determination of transition element size since we're not
+ * going to be using it to determine hashtable limits. This
+ * simplifies the code for hypothetical set functions.
+ */
+
+ if (aggref->aggorder == NIL && aggref->aggdistinct == NIL)
{
- TargetEntry *tle = (TargetEntry *) lfirst(l);
+ Assert(!aggref->isordset);
- if (!tle->resjunk)
- inputTypes[numArguments++] = exprType((Node *) tle->expr);
- }
+ /* extract argument types (ignoring any ORDER BY expressions) */
+ inputTypes = (Oid *) palloc(sizeof(Oid) * FUNC_MAX_ARGS);
- /* resolve actual type of transition state, if polymorphic */
- if (IsPolymorphicType(aggtranstype))
- {
- /* have to fetch the agg's declared input types... */
- Oid *declaredArgTypes;
- int agg_nargs;
-
- (void) get_func_signature(aggref->aggfnoid,
- &declaredArgTypes, &agg_nargs);
- Assert(agg_nargs == numArguments);
- aggtranstype = enforce_generic_type_consistency(inputTypes,
- declaredArgTypes,
- agg_nargs,
- aggtranstype,
- false);
- pfree(declaredArgTypes);
- }
+ numArguments = get_aggregate_argtypes(aggref, inputTypes, NULL);
- /*
- * If the transition type is pass-by-value then it doesn't add
- * anything to the required size of the hashtable. If it is
- * pass-by-reference then we have to add the estimated size of the
- * value itself, plus palloc overhead.
- */
- if (!get_typbyval(aggtranstype))
- {
- int32 aggtranstypmod;
- int32 avgwidth;
+ /* resolve actual type of transition state, if polymorphic */
+ if (OidIsValid(aggtranstype) && IsPolymorphicType(aggtranstype))
+ {
+ /* have to fetch the agg's declared input types... */
+ Oid *declaredArgTypes;
+ int agg_nargs;
+
+ (void) get_func_signature(aggref->aggfnoid,
+ &declaredArgTypes, &agg_nargs);
+
+ /*
+ * if variadic "any", might be more actual args than declared
+ * args, but these extra args can't influence the determination
+ * of polymorphic transition or result type.
+ */
+ Assert(agg_nargs <= numArguments);
+
+ aggtranstype = enforce_generic_type_consistency(inputTypes,
+ declaredArgTypes,
+ agg_nargs,
+ aggtranstype,
+ false);
+ pfree(declaredArgTypes);
+ }
/*
- * If transition state is of same type as first input, assume it's
- * the same typmod (same width) as well. This works for cases
- * like MAX/MIN and is probably somewhat reasonable otherwise.
+ * If the transition type is pass-by-value then it doesn't add
+ * anything to the required size of the hashtable. If it is
+ * pass-by-reference then we have to add the estimated size of the
+ * value itself, plus palloc overhead.
*/
- if (numArguments > 0 && aggtranstype == inputTypes[0])
- aggtranstypmod = exprTypmod((Node *) linitial(aggref->args));
- else
- aggtranstypmod = -1;
+ if (OidIsValid(aggtranstype) && !get_typbyval(aggtranstype))
+ {
+ int32 aggtranstypmod;
+ int32 avgwidth;
+
+ /*
+ * If transition state is of same type as first input, assume it's
+ * the same typmod (same width) as well. This works for cases
+ * like MAX/MIN and is probably somewhat reasonable otherwise.
+ */
+ if (numArguments > 0 && aggtranstype == inputTypes[0])
+ aggtranstypmod = exprTypmod((Node *) linitial(aggref->args));
+ else
+ aggtranstypmod = -1;
- avgwidth = get_typavgwidth(aggtranstype, aggtranstypmod);
- avgwidth = MAXALIGN(avgwidth);
+ avgwidth = get_typavgwidth(aggtranstype, aggtranstypmod);
+ avgwidth = MAXALIGN(avgwidth);
- costs->transitionSpace += avgwidth + 2 * sizeof(void *);
+ costs->transitionSpace += avgwidth + 2 * sizeof(void *);
+ }
+ else if (aggtranstype == INTERNALOID)
+ {
+ /*
+ * INTERNAL transition type is a special case: although INTERNAL
+ * is pass-by-value, it's almost certainly being used as a pointer
+ * to some large data structure. We assume usage of
+ * ALLOCSET_DEFAULT_INITSIZE, which is a good guess if the data is
+ * being kept in a private memory context, as is done by
+ * array_agg() for instance.
+ */
+ costs->transitionSpace += ALLOCSET_DEFAULT_INITSIZE;
+ }
+
+ pfree(inputTypes);
}
- else if (aggtranstype == INTERNALOID)
+ else
{
- /*
- * INTERNAL transition type is a special case: although INTERNAL
- * is pass-by-value, it's almost certainly being used as a pointer
- * to some large data structure. We assume usage of
- * ALLOCSET_DEFAULT_INITSIZE, which is a good guess if the data is
- * being kept in a private memory context, as is done by
- * array_agg() for instance.
- */
- costs->transitionSpace += ALLOCSET_DEFAULT_INITSIZE;
+ costs->transitionSpace = work_mem; /* just in case */
}
/*
@@ -3826,7 +3846,7 @@ recheck_cast_function_args(List *args, Oid result_type, HeapTuple func_tuple)
elog(ERROR, "function's resolved result type changed during planning");
/* perform any necessary typecasting of arguments */
- make_fn_arguments(NULL, args, actual_arg_types, declared_arg_types);
+ make_fn_arguments(NULL, args, NULL, actual_arg_types, declared_arg_types, false);
}
/*
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index a9d1fec..ae168ad 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -951,7 +951,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
&qry->targetList,
EXPR_KIND_ORDER_BY,
true /* fix unknowns */ ,
- false /* allow SQL92 rules */ );
+ false /* allow SQL92 rules */,
+ false /* don't add duplicates */);
qry->groupClause = transformGroupClause(pstate,
stmt->groupClause,
@@ -1211,7 +1212,8 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
&qry->targetList,
EXPR_KIND_ORDER_BY,
true /* fix unknowns */ ,
- false /* allow SQL92 rules */ );
+ false /* allow SQL92 rules */,
+ false /* don't add duplicates */ );
qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
EXPR_KIND_OFFSET, "OFFSET");
@@ -1435,7 +1437,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
&qry->targetList,
EXPR_KIND_ORDER_BY,
false /* no unknowns expected */ ,
- false /* allow SQL92 rules */ );
+ false /* allow SQL92 rules */ ,
+ false /* don't add duplicates */ );
/* restore namespace, remove jrte from rtable */
pstate->p_namespace = sv_namespace;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 363c603..869d876 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -494,6 +494,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
%type <node> filter_clause
+%type <list> within_group_clause
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -596,7 +597,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
VERBOSE VERSION_P VIEW VOLATILE
- WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
+ WHEN WHERE WITHIN WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
XMLPI XMLROOT XMLSERIALIZE
@@ -3664,7 +3665,7 @@ AlterExtensionContentsStmt:
n->action = $4;
n->objtype = OBJECT_AGGREGATE;
n->objname = $6;
- n->objargs = extractArgTypes($7);
+ n->objargs = extractArgTypes(linitial($7));
$$ = (Node *)n;
}
| ALTER EXTENSION name add_drop CAST '(' Typename AS Typename ')'
@@ -5243,7 +5244,7 @@ CommentStmt:
CommentStmt *n = makeNode(CommentStmt);
n->objtype = OBJECT_AGGREGATE;
n->objname = $4;
- n->objargs = extractArgTypes($5);
+ n->objargs = extractArgTypes(linitial($5));
n->comment = $7;
$$ = (Node *) n;
}
@@ -5409,7 +5410,7 @@ SecLabelStmt:
n->provider = $3;
n->objtype = OBJECT_AGGREGATE;
n->objname = $6;
- n->objargs = extractArgTypes($7);
+ n->objargs = extractArgTypes(linitial($7));
n->label = $9;
$$ = (Node *) n;
}
@@ -6409,9 +6410,53 @@ aggr_arg: func_arg
}
;
-/* Zero-argument aggregates are named with * for consistency with COUNT(*) */
-aggr_args: '(' aggr_args_list ')' { $$ = $2; }
- | '(' '*' ')' { $$ = NIL; }
+/*
+ * Aggregate args (for create aggregate, etc.) are treated as follows:
+ *
+ * (*) - no args
+ * (func_arg,func_arg,...) - normal agg with args
+ * () within group (func_arg,...) - ordered set func with no direct args
+ * (func_arg,...) within group (func_arg,...) - ordered set func with args
+ * (func_arg,...) within group (*) - ordered set func variadic special case
+ *
+ * This doesn't correspond to anything in the spec because the spec doesn't
+ * have any DDL to create or modify ordered set functions, so we're winging
+ * it here.
+ *
+ * Almost everything we do with an ordered set function treats its arguments
+ * as though they were a single list, with the direct and grouped arg types
+ * concatenated. So for simplicity, we construct a single list here.
+ *
+ * But we still need to know when creating an agg (but not for referring to it
+ * later) where the division between direct and ordered args is; so this
+ * production returns a pair (arglist,num) where num is the number of direct
+ * args, or -1 if no within group clause was used. Most users of aggr_args,
+ * other than CREATE AGGREGATE, therefore only need to pay attention to
+ * linitial($n).
+ */
+
+aggr_args: '(' '*' ')'
+ {
+ $$ = list_make2(NIL, makeInteger(-1));
+ }
+ | '(' aggr_args_list ')'
+ {
+ $$ = list_make2($2, makeInteger(-1));
+ }
+ | '(' ')' WITHIN GROUP_P '(' aggr_args_list ')'
+ {
+ $$ = list_make2($6, makeInteger(0));
+ }
+ | '(' aggr_args_list ')' WITHIN GROUP_P '(' aggr_args_list ')'
+ {
+ int n = list_length($2);
+ $$ = list_make2(list_concat($2,$7), makeInteger(n));
+ }
+ | '(' aggr_args_list ')' WITHIN GROUP_P '(' '*' ')'
+ {
+ int n = list_length($2);
+ $$ = list_make2($2, makeInteger(n));
+ }
;
aggr_args_list:
@@ -6617,7 +6662,7 @@ RemoveAggrStmt:
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_AGGREGATE;
n->objects = list_make1($3);
- n->arguments = list_make1(extractArgTypes($4));
+ n->arguments = list_make1(extractArgTypes(linitial($4)));
n->behavior = $5;
n->missing_ok = false;
n->concurrent = false;
@@ -6628,7 +6673,7 @@ RemoveAggrStmt:
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_AGGREGATE;
n->objects = list_make1($5);
- n->arguments = list_make1(extractArgTypes($6));
+ n->arguments = list_make1(extractArgTypes(linitial($6)));
n->behavior = $7;
n->missing_ok = true;
n->concurrent = false;
@@ -6844,7 +6889,7 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
RenameStmt *n = makeNode(RenameStmt);
n->renameType = OBJECT_AGGREGATE;
n->object = $3;
- n->objarg = extractArgTypes($4);
+ n->objarg = extractArgTypes(linitial($4));
n->newname = $7;
n->missing_ok = false;
$$ = (Node *)n;
@@ -7318,7 +7363,7 @@ AlterObjectSchemaStmt:
AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
n->objectType = OBJECT_AGGREGATE;
n->object = $3;
- n->objarg = extractArgTypes($4);
+ n->objarg = extractArgTypes(linitial($4));
n->newschema = $7;
n->missing_ok = false;
$$ = (Node *)n;
@@ -7547,7 +7592,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_AGGREGATE;
n->object = $3;
- n->objarg = extractArgTypes($4);
+ n->objarg = extractArgTypes(linitial($4));
n->newowner = $7;
$$ = (Node *)n;
}
@@ -9435,6 +9480,11 @@ sortby: a_expr USING qual_all_Op opt_nulls_order
;
+within_group_clause:
+ WITHIN GROUP_P '(' sort_clause ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
select_limit:
limit_clause offset_clause { $$ = list_make2($2, $1); }
| offset_clause limit_clause { $$ = list_make2($1, $2); }
@@ -11140,12 +11190,35 @@ func_application: func_name '(' ')'
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
-func_expr: func_application filter_clause over_clause
+func_expr: func_application within_group_clause filter_clause over_clause
{
- FuncCall *n = (FuncCall*)$1;
- n->agg_filter = $2;
- n->over = $3;
- $$ = (Node*)n;
+ FuncCall *n = (FuncCall *) $1;
+ /*
+ * the order clause for WITHIN GROUP and the one
+ * for aggregate ORDER BY share a field, so we
+ * have to check here that at most one is present.
+ * We check for DISTINCT here to give a better
+ * error position. Other consistency checks are
+ * deferred to parse_func.c or parse_agg.c
+ */
+ if ($2 != NIL)
+ {
+ if (n->agg_order != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot have multiple ORDER BY clauses with WITHIN GROUP"),
+ parser_errposition(@2)));
+ if (n->agg_distinct)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot have DISTINCT and WITHIN GROUP together"),
+ parser_errposition(@2)));
+ n->agg_order = $2;
+ n->has_within_group = TRUE;
+ }
+ n->agg_filter = $3;
+ n->over = $4;
+ $$ = (Node *) n;
}
| func_expr_common_subexpr
{ $$ = $1; }
@@ -12704,6 +12777,7 @@ unreserved_keyword:
| VIEW
| VOLATILE
| WHITESPACE_P
+ | WITHIN
| WITHOUT
| WORK
| WRAPPER
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 98cb58a..23954a0 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -44,7 +44,9 @@ typedef struct
int sublevels_up;
} check_ungrouped_columns_context;
-static int check_agg_arguments(ParseState *pstate, List *args, Expr *filter);
+static int check_agg_arguments(ParseState *pstate,
+ List *args,
+ List *agg_ordset, Expr *filter);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
@@ -75,7 +77,8 @@ static bool check_ungrouped_columns_walker(Node *node,
*/
void
transformAggregateCall(ParseState *pstate, Aggref *agg,
- List *args, List *aggorder, bool agg_distinct)
+ List *args, List *aggorder,
+ bool agg_distinct, bool agg_within_group)
{
List *tlist;
List *torder;
@@ -93,12 +96,24 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
*/
tlist = NIL;
attno = 1;
- foreach(lc, args)
+
+ if (agg_within_group)
{
- Expr *arg = (Expr *) lfirst(lc);
- TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false);
+ agg->isordset = TRUE;
+ agg->orddirectargs = args;
+ }
+ else
+ {
+ foreach(lc, args)
+ {
+ Expr *arg = (Expr *) lfirst(lc);
+ TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false);
- tlist = lappend(tlist, tle);
+ tlist = lappend(tlist, tle);
+ }
+
+ agg->isordset = FALSE;
+ agg->orddirectargs = NIL;
}
/*
@@ -109,6 +124,11 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
*
* We need to mess with p_next_resno since it will be used to number any
* new targetlist entries.
+ *
+ * If and only if we're doing a WITHIN GROUP list, we preserve any
+ * duplicate expressions in the sort clause. This is needed because the
+ * sort clause of WITHIN GROUP is really an argument list, and we must
+ * keep the number and content of entries matching the specified input.
*/
save_next_resno = pstate->p_next_resno;
pstate->p_next_resno = attno;
@@ -118,7 +138,8 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
&tlist,
EXPR_KIND_ORDER_BY,
true /* fix unknowns */ ,
- true /* force SQL99 rules */ );
+ true /* force SQL99 rules */ ,
+ agg_within_group /* keep duplicates? */ );
/*
* If we have DISTINCT, transform that to produce a distinctList.
@@ -160,7 +181,8 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
- min_varlevel = check_agg_arguments(pstate, agg->args, agg->aggfilter);
+ min_varlevel = check_agg_arguments(pstate,
+ agg->args, agg->orddirectargs, agg->aggfilter);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
@@ -312,7 +334,7 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
* which we can't know until we finish scanning the arguments.
*/
static int
-check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
+check_agg_arguments(ParseState *pstate, List *args, List *agg_ordset, Expr *filter)
{
int agglevel;
check_agg_arguments_context context;
@@ -330,6 +352,9 @@ check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
check_agg_arguments_walker,
(void *) &context);
+ (void) expression_tree_walker((Node *) agg_ordset, check_agg_arguments_walker,
+ (void *) &context);
+
/*
* If we found no vars nor aggs at all, it's a level-zero aggregate;
* otherwise, its level is the minimum of vars or aggs.
@@ -353,8 +378,8 @@ check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("aggregate function calls cannot be nested"),
parser_errposition(pstate,
- locate_agg_of_level((Node *) args,
- agglevel))));
+ locate_agg_of_level((Node *) args,
+ agglevel))));
return agglevel;
}
@@ -823,8 +848,16 @@ check_ungrouped_columns_walker(Node *node,
* We do need to look at aggregates of lower levels, however.
*/
if (IsA(node, Aggref) &&
- (int) ((Aggref *) node)->agglevelsup >= context->sublevels_up)
+ (int) ((Aggref *) node)->agglevelsup > context->sublevels_up)
+ {
return false;
+ }
+ else if (IsA(node, Aggref) &&
+ (int) ((Aggref *) node)->agglevelsup == context->sublevels_up)
+ {
+ return check_ungrouped_columns_walker((Node*)(((Aggref *)node)->orddirectargs),
+ context);
+ }
/*
* If we have any GROUP BY items that are not simple Vars, check to see if
@@ -1042,3 +1075,98 @@ build_aggregate_fnexprs(Oid *agg_input_types,
agg_input_collation,
COERCE_EXPLICIT_CALL);
}
+
+void
+build_orderedset_fnexprs(Oid *agg_input_types,
+ int agg_num_inputs,
+ bool agg_variadic,
+ Oid agg_result_type,
+ Oid agg_input_collation,
+ Oid *agg_input_collation_array,
+ Oid finalfn_oid,
+ Expr **finalfnexpr)
+{
+ FuncExpr *fexpr;
+ Param *argp;
+ List *args = NIL;
+ int i = 0;
+
+ /*
+ * Build arg list to use in the finalfn FuncExpr node. We really only care
+ * that finalfn can discover the actual argument types at runtime using
+ * get_fn_expr_argtype(), so it's okay to use Param nodes that don't
+ * correspond to any real Param.
+ */
+ for (i = 0; i < agg_num_inputs; i++)
+ {
+ argp = makeNode(Param);
+ argp->paramkind = PARAM_EXEC;
+ argp->paramid = -1;
+ argp->paramtype = agg_input_types[i];
+ argp->paramtypmod = -1;
+ argp->paramcollid = agg_input_collation_array[i];
+ argp->location = -1;
+
+ args = lappend(args, argp);
+ }
+
+ fexpr = makeFuncExpr(finalfn_oid,
+ agg_result_type,
+ args,
+ InvalidOid,
+ agg_input_collation,
+ COERCE_EXPLICIT_CALL);
+ fexpr->funcvariadic = agg_variadic;
+ *finalfnexpr = (Expr *) fexpr;
+}
+
+int get_aggregate_argtypes(Aggref *aggref, Oid *inputTypes, Oid *inputCollations)
+{
+ int numArguments = 0;
+ ListCell *lc;
+
+ if (!(aggref->isordset))
+ {
+ foreach(lc, aggref->args)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+ if (!tle->resjunk)
+ {
+ inputTypes[numArguments] = exprType((Node *) tle->expr);
+ if (inputCollations != NULL)
+ inputCollations[numArguments] = exprCollation((Node *) tle->expr);
+ ++numArguments;
+ }
+ }
+ }
+ else
+ {
+ foreach(lc, aggref->orddirectargs)
+ {
+ Node *expr_orddirectargs = lfirst(lc);
+
+ inputTypes[numArguments] = exprType(expr_orddirectargs);
+ if (inputCollations != NULL)
+ inputCollations[numArguments] = exprCollation(expr_orddirectargs);
+
+ ++numArguments;
+ }
+
+ if (!(aggref->ishypothetical))
+ {
+ foreach(lc, aggref->args)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+ inputTypes[numArguments] = exprType((Node *) tle->expr);
+ if (inputCollations != NULL)
+ inputCollations[numArguments] = exprCollation((Node *) tle->expr);
+
+ ++numArguments;
+ }
+ }
+ }
+
+ return numArguments;
+}
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index ea90e58..88484b2 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -71,7 +71,8 @@ static void checkExprIsVarFree(ParseState *pstate, Node *n,
static TargetEntry *findTargetlistEntrySQL92(ParseState *pstate, Node *node,
List **tlist, ParseExprKind exprKind);
static TargetEntry *findTargetlistEntrySQL99(ParseState *pstate, Node *node,
- List **tlist, ParseExprKind exprKind);
+ List **tlist, ParseExprKind exprKind,
+ bool keepDuplicates);
static int get_matching_location(int sortgroupref,
List *sortgrouprefs, List *exprs);
static List *addTargetToSortList(ParseState *pstate, TargetEntry *tle,
@@ -1476,7 +1477,7 @@ findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
/*
* Otherwise, we have an expression, so process it per SQL99 rules.
*/
- return findTargetlistEntrySQL99(pstate, node, tlist, exprKind);
+ return findTargetlistEntrySQL99(pstate, node, tlist, exprKind, false);
}
/*
@@ -1491,10 +1492,11 @@ findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
* node the ORDER BY, GROUP BY, etc expression to be matched
* tlist the target list (passed by reference so we can append to it)
* exprKind identifies clause type being processed
+ * keepDuplicates if true, don't try and match to any existing entry
*/
static TargetEntry *
findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
- ParseExprKind exprKind)
+ ParseExprKind exprKind, bool keepDuplicates)
{
TargetEntry *target_result;
ListCell *tl;
@@ -1509,24 +1511,27 @@ findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
*/
expr = transformExpr(pstate, node, exprKind);
- foreach(tl, *tlist)
+ if (!keepDuplicates)
{
- TargetEntry *tle = (TargetEntry *) lfirst(tl);
- Node *texpr;
+ foreach(tl, *tlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(tl);
+ Node *texpr;
- /*
- * Ignore any implicit cast on the existing tlist expression.
- *
- * This essentially allows the ORDER/GROUP/etc item to adopt the same
- * datatype previously selected for a textually-equivalent tlist item.
- * There can't be any implicit cast at top level in an ordinary SELECT
- * tlist at this stage, but the case does arise with ORDER BY in an
- * aggregate function.
- */
- texpr = strip_implicit_coercions((Node *) tle->expr);
+ /*
+ * Ignore any implicit cast on the existing tlist expression.
+ *
+ * This essentially allows the ORDER/GROUP/etc item to adopt the same
+ * datatype previously selected for a textually-equivalent tlist item.
+ * There can't be any implicit cast at top level in an ordinary SELECT
+ * tlist at this stage, but the case does arise with ORDER BY in an
+ * aggregate function.
+ */
+ texpr = strip_implicit_coercions((Node *) tle->expr);
- if (equal(expr, texpr))
- return tle;
+ if (equal(expr, texpr))
+ return tle;
+ }
}
/*
@@ -1568,7 +1573,7 @@ transformGroupClause(ParseState *pstate, List *grouplist,
if (useSQL99)
tle = findTargetlistEntrySQL99(pstate, gexpr,
- targetlist, exprKind);
+ targetlist, exprKind, false);
else
tle = findTargetlistEntrySQL92(pstate, gexpr,
targetlist, exprKind);
@@ -1635,11 +1640,14 @@ transformSortClause(ParseState *pstate,
List **targetlist,
ParseExprKind exprKind,
bool resolveUnknown,
- bool useSQL99)
+ bool useSQL99,
+ bool keepDuplicates)
{
List *sortlist = NIL;
ListCell *olitem;
+ Assert(useSQL99 || !keepDuplicates);
+
foreach(olitem, orderlist)
{
SortBy *sortby = (SortBy *) lfirst(olitem);
@@ -1647,7 +1655,7 @@ transformSortClause(ParseState *pstate,
if (useSQL99)
tle = findTargetlistEntrySQL99(pstate, sortby->node,
- targetlist, exprKind);
+ targetlist, exprKind, keepDuplicates);
else
tle = findTargetlistEntrySQL92(pstate, sortby->node,
targetlist, exprKind);
@@ -1717,7 +1725,8 @@ transformWindowDefinitions(ParseState *pstate,
targetlist,
EXPR_KIND_WINDOW_ORDER,
true /* fix unknowns */ ,
- true /* force SQL99 rules */ );
+ true /* force SQL99 rules */,
+ false /* don't add duplicates */);
partitionClause = transformGroupClause(pstate,
windef->partitionClause,
targetlist,
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index fe57c59..f3499fc 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -73,7 +73,9 @@ typedef struct
static bool assign_query_collations_walker(Node *node, ParseState *pstate);
static bool assign_collations_walker(Node *node,
assign_collations_context *context);
-
+static void assign_aggregate_collations(Aggref *aggref,
+ assign_collations_context *context,
+ assign_collations_context *loccontext);
/*
* assign_query_collations()
@@ -564,44 +566,16 @@ assign_collations_walker(Node *node, assign_collations_context *context)
case T_Aggref:
{
/*
- * Aggref is a special case because expressions
- * used only for ordering shouldn't be taken to
- * conflict with each other or with regular args.
- * So we apply assign_expr_collations() to them
- * rather than passing down our loccontext.
- *
- * Note that we recurse to each TargetEntry, not
- * directly to its contained expression, so that
- * the case above for T_TargetEntry will apply
- * appropriate checks to agg ORDER BY items.
- *
- * Likewise, we assign collations for the (bool)
- * expression in aggfilter, independently of any
- * other args.
- *
- * We need not recurse into the aggorder or
- * aggdistinct lists, because those contain only
- * SortGroupClause nodes which we need not
- * process.
+ * Aggref is special enough that we give it its own
+ * function. The FILTER clause is independent of the
+ * rest of the aggregate, however.
*/
Aggref *aggref = (Aggref *) node;
- ListCell *lc;
- foreach(lc, aggref->args)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(lc);
-
- Assert(IsA(tle, TargetEntry));
- if (tle->resjunk)
- assign_expr_collations(context->pstate,
- (Node *) tle);
- else
- (void) assign_collations_walker((Node *) tle,
- &loccontext);
- }
+ assign_aggregate_collations(aggref, context, &loccontext);
assign_expr_collations(context->pstate,
- (Node *) aggref->aggfilter);
+ (Node *) aggref->aggfilter);
}
break;
case T_WindowFunc:
@@ -802,3 +776,159 @@ assign_collations_walker(Node *node, assign_collations_context *context)
return false;
}
+
+
+/*
+ * Aggref is a special case because expressions used only for ordering
+ * shouldn't be taken to conflict with each other or with regular args. So we
+ * apply assign_expr_collations() to them rather than passing down our
+ * loccontext.
+ *
+ * Note that we recurse to each TargetEntry, not directly to its contained
+ * expression, so that the case above for T_TargetEntry will apply appropriate
+ * checks to agg ORDER BY items.
+ *
+ * We need not recurse into the aggorder or aggdistinct lists, because those
+ * contain only SortGroupClause nodes which we need not process.
+ *
+ * For ordered set functions, it's unfortunately unclear how best to proceed.
+ * The spec-defined inverse distribution functions have only one sort column
+ * and don't allow collatable types, but this is clearly unsatisfactory in the
+ * general case. Compromise by taking the sort column as part of the collation
+ * determination if, and only if, there is only one such column, and force the
+ * final choice of input collation down into the sort column if need be; but
+ * don't error out unless actually necessary (leaving it up to the function to
+ * handle the issue at runtime). This ugly wart is justified by the fact that
+ * there seems to be no other good way to get a result collation for
+ * percentile_* applied to a collatable type.
+ *
+ * But hypothetical set functions are special; they must have
+ * pairwise-assigned collations for each matching pair of args, and again we
+ * need to force the final choice of collation down into the sort column to
+ * ensure that the sort happens on the chosen collation. If there are any
+ * additional args (not allowed in the spec, but a user-defined function might
+ * have some), those contribute to the result collation in the normal way.
+ * (The hypothetical paired args never contribute to the result collation at
+ * all.)
+ */
+
+static Expr *
+relabel_expr_collation(Expr *expr, Oid newcollation)
+{
+ RelabelType *node = makeNode(RelabelType);
+ node->arg = expr;
+ node->resulttype = exprType((Node *)expr);
+ node->resulttypmod = exprTypmod((Node *)expr);
+ node->resultcollid = newcollation;
+ node->relabelformat = COERCE_IMPLICIT_CAST;
+ node->location = exprLocation((Node *)expr);
+ return (Expr *) node;
+}
+
+static void
+assign_aggregate_collations(Aggref *aggref,
+ assign_collations_context *context,
+ assign_collations_context *loccontext)
+{
+ ListCell *lc;
+
+ if (aggref->ishypothetical)
+ {
+ /*-
+ * Hypothetical set function, i.e.
+ * func(..., a,b,c,...) within group (p,q,r,...)
+ *
+ * Any initial set of direct args (before "a") contributes to the
+ * result collation in the usual way for function args. But none of
+ * a,b,c... or p,q,r... contribute at all; instead, they must be
+ * paired up (as though UNIONed) and the sorted col's collation forced
+ * to the chosen value (so that we sort it correctly).
+ */
+ int initial_args = list_length(aggref->orddirectargs) - list_length(aggref->args);
+ ListCell *h_arg = list_head(aggref->orddirectargs);
+ ListCell *s_arg = list_head(aggref->args);
+
+ Assert(initial_args >= 0);
+
+ while (initial_args-- > 0)
+ {
+ (void) assign_collations_walker((Node *) lfirst(h_arg), loccontext);
+ h_arg = lnext(h_arg);
+ }
+
+ for_each_cell(h_arg,h_arg)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(s_arg);
+ Oid coll = select_common_collation(context->pstate,
+ list_make2(lfirst(h_arg),lfirst(s_arg)),
+ false);
+
+ /*
+ * we can only get InvalidOid here if the type is not collatable,
+ * so no need to try and relabel in that case.
+ */
+
+ if (OidIsValid(coll)
+ && coll != exprCollation((Node *)(tle->expr)))
+ {
+ tle->expr = relabel_expr_collation(tle->expr, coll);
+ }
+
+ s_arg = lnext(s_arg);
+ }
+ }
+ else if (aggref->isordset && list_length(aggref->args) == 1)
+ {
+ /*
+ * Ordered set func with one sorted arg
+ */
+ TargetEntry *tle = (TargetEntry *) linitial(aggref->args);
+
+ /* do the TLE first so that it won't error out on conflicts */
+
+ (void) assign_collations_walker((Node *) tle,
+ loccontext);
+
+ (void) assign_collations_walker((Node *) aggref->orddirectargs,
+ loccontext);
+
+ /*
+ * If the sort col is a collatable type, and we chose a collation,
+ * and it's not the one the sort col already has, then force the
+ * sort col's collation (which can't have been explicit) to the
+ * chosen one. Otherwise leave it alone.
+ */
+ if (type_is_collatable(exprType((Node *)(tle->expr)))
+ && (loccontext->strength == COLLATE_IMPLICIT
+ || loccontext->strength == COLLATE_EXPLICIT)
+ && exprCollation((Node *)(tle->expr)) != loccontext->collation)
+ {
+ tle->expr = relabel_expr_collation(tle->expr, loccontext->collation);
+ }
+ }
+ else
+ {
+ /*
+ * For this case, we do the direct args (if any) together, as is
+ * normal for functions, but args which are either used only for
+ * sorting or are only part of a WITHIN GROUP are processed
+ * individually.
+ */
+
+ (void) assign_collations_walker((Node *) aggref->orddirectargs,
+ loccontext);
+
+ foreach(lc, aggref->args)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+ Assert(IsA(tle, TargetEntry));
+ if (tle->resjunk)
+ assign_expr_collations(context->pstate,
+ (Node *) tle);
+ else
+ (void) assign_collations_walker((Node *) tle,
+ loccontext);
+ }
+ }
+}
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 68b711d..1800a68 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -463,8 +463,8 @@ transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
newresult = ParseFuncOrColumn(pstate,
list_make1(n),
list_make1(result),
- NIL, NULL, false, false, false,
- NULL, true, location);
+ location,
+ NULL);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
@@ -631,8 +631,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
node = ParseFuncOrColumn(pstate,
list_make1(makeString(colname)),
list_make1(node),
- NIL, NULL, false, false, false,
- NULL, true, cref->location);
+ cref->location, NULL);
}
break;
}
@@ -676,8 +675,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
node = ParseFuncOrColumn(pstate,
list_make1(makeString(colname)),
list_make1(node),
- NIL, NULL, false, false, false,
- NULL, true, cref->location);
+ cref->location, NULL);
}
break;
}
@@ -734,8 +732,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
node = ParseFuncOrColumn(pstate,
list_make1(makeString(colname)),
list_make1(node),
- NIL, NULL, false, false, false,
- NULL, true, cref->location);
+ cref->location, NULL);
}
break;
}
@@ -1242,38 +1239,20 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
{
List *targs;
ListCell *args;
- Expr *tagg_filter;
/* Transform the list of arguments ... */
targs = NIL;
foreach(args, fn->args)
{
- targs = lappend(targs, transformExprRecurse(pstate,
- (Node *) lfirst(args)));
+ targs = lappend(targs, transformExprRecurse(pstate, (Node *) lfirst(args)));
}
- /*
- * Transform the aggregate filter using transformWhereClause(), to which
- * FILTER is virtually identical...
- */
- tagg_filter = NULL;
- if (fn->agg_filter != NULL)
- tagg_filter = (Expr *)
- transformWhereClause(pstate, (Node *) fn->agg_filter,
- EXPR_KIND_FILTER, "FILTER");
-
/* ... and hand off to ParseFuncOrColumn */
return ParseFuncOrColumn(pstate,
fn->funcname,
targs,
- fn->agg_order,
- tagg_filter,
- fn->agg_star,
- fn->agg_distinct,
- fn->func_variadic,
- fn->over,
- false,
- fn->location);
+ fn->location,
+ fn);
}
static Node *
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 2bd24c8..2ea5aa9 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -17,16 +17,19 @@
#include "access/htup_details.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_aggregate.h"
#include "funcapi.h"
#include "lib/stringinfo.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
#include "parser/parse_type.h"
+#include "parser/parse_expr.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
@@ -56,15 +59,21 @@ static Node *ParseComplexProjection(ParseState *pstate, char *funcname,
* Also, when is_column is true, we return NULL on failure rather than
* reporting a no-such-function error.
*
- * The argument expressions (in fargs) and filter must have been transformed
- * already. But the agg_order expressions, if any, have not been.
+ * The argument expressions (in fargs) must have been transformed
+ * already.
*/
Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
- List *agg_order, Expr *agg_filter,
- bool agg_star, bool agg_distinct, bool func_variadic,
- WindowDef *over, bool is_column, int location)
+ int location, FuncCall *fn)
{
+ List *agg_order = (fn ? fn->agg_order : NIL);
+ Expr *agg_filter = NULL;
+ bool agg_star = (fn ? fn->agg_star : false);
+ bool agg_distinct = (fn ? fn->agg_distinct : false);
+ bool agg_within_group = (fn ? fn->has_within_group : false);
+ bool func_variadic = (fn ? fn->func_variadic : false);
+ WindowDef *over = (fn ? fn->over : NULL);
+ bool is_column = (fn == NULL);
Oid rettype;
Oid funcid;
ListCell *l;
@@ -81,6 +90,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
int nvargs;
Oid vatype;
FuncDetailCode fdresult;
+ int number_of_args = -1;
+ bool isordsetfunc = false;
+ bool ishypotheticalsetfunc = false;
+
+ /* Check if the function has WITHIN GROUP as well as distinct. */
+ Assert(!(agg_within_group && agg_distinct));
/*
* Most of the rest of the parser just assumes that functions do not have
@@ -98,6 +113,15 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
parser_errposition(pstate, location)));
/*
+ * Transform the aggregate filter using transformWhereClause(), to which
+ * FILTER is virtually identical...
+ */
+ if (fn && fn->agg_filter != NULL)
+ agg_filter = (Expr *)
+ transformWhereClause(pstate, (Node *) fn->agg_filter,
+ EXPR_KIND_FILTER, "FILTER");
+
+ /*
* Extract arg type info in preparation for function lookup.
*
* If any arguments are Param markers of type VOID, we discard them from
@@ -163,6 +187,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
}
}
+ if (agg_within_group && argnames)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ordered set functions cannot use named arguments"),
+ parser_errposition(pstate, location)));
+
if (fargs)
{
first_arg = linitial(fargs);
@@ -170,6 +200,26 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
}
/*
+ * If WITHIN GROUP is present, we need to call transformExpr on each
+ * SortBy node in agg_order, then call exprType and append to
+ * actual_arg_types, in order to get the types of values in WITHIN GROUP
+ * clause.
+ */
+ if (agg_within_group)
+ {
+ Assert(agg_order != NIL);
+
+ foreach(l, agg_order)
+ {
+ SortBy *arg = (SortBy *) lfirst(l);
+
+ arg->node = transformExpr(pstate, arg->node, EXPR_KIND_ORDER_BY);
+
+ actual_arg_types[nargs++] = exprType(arg->node);
+ }
+ }
+
+ /*
* Check for column projection: if function has one argument, and that
* argument is of complex type, and function name is not qualified, then
* the "function call" could be a projection. We also check that there
@@ -247,6 +297,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
errmsg("DISTINCT specified, but %s is not an aggregate function",
NameListToString(funcname)),
parser_errposition(pstate, location)));
+ if (agg_within_group)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("WITHIN GROUP specified, but %s is not an ordered set function",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
if (agg_order != NIL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -266,6 +322,53 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
NameListToString(funcname)),
parser_errposition(pstate, location)));
}
+ else if (fdresult == FUNCDETAIL_AGGREGATE)
+ {
+ HeapTuple tup;
+ Form_pg_aggregate classForm;
+
+ tup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for aggregate %u", funcid);
+
+ classForm = (Form_pg_aggregate) GETSTRUCT(tup);
+ isordsetfunc = classForm->aggisordsetfunc;
+
+ if (isordsetfunc)
+ {
+ if (classForm->aggordnargs == -2)
+ {
+ ishypotheticalsetfunc = true;
+
+ if (nvargs != 2*list_length(agg_order))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("function %s has %d ordering columns but %d hypothetical arguments",
+ NameListToString(funcname), list_length(agg_order), (nvargs - list_length(agg_order))),
+ parser_errposition(pstate, location)));
+ }
+ else
+ {
+ number_of_args = classForm->aggordnargs;
+ }
+
+ if (!agg_within_group)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("WITHIN GROUP is required for call to ordered set function %s",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
+
+ if (over)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("OVER clause not supported for call to ordered set function %s",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
+ }
+
+ ReleaseSysCache(tup);
+ }
else if (!(fdresult == FUNCDETAIL_AGGREGATE ||
fdresult == FUNCDETAIL_WINDOWFUNC))
{
@@ -351,13 +454,16 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
false);
/* perform the necessary typecasting of arguments */
- make_fn_arguments(pstate, fargs, actual_arg_types, declared_arg_types);
+ make_fn_arguments(pstate, fargs, (isordsetfunc) ? agg_order : NIL,
+ actual_arg_types,
+ declared_arg_types,
+ ishypotheticalsetfunc);
/*
* If it's a variadic function call, transform the last nvargs arguments
* into an array --- unless it's an "any" variadic.
*/
- if (nvargs > 0 && declared_arg_types[nargs - 1] != ANYOID)
+ if (nvargs > 0 && vatype != ANYOID)
{
ArrayExpr *newa = makeNode(ArrayExpr);
int non_var_args = nargs - nvargs;
@@ -388,16 +494,31 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
* When function is called with an explicit VARIADIC labeled parameter,
* and the declared_arg_type is "any", then sanity check the actual
* parameter type now - it must be an array.
+ *
+ * Also, it can't be a hypothetical set function, and if it's an ordered
+ * set function, the variadic labeled parameter is the last _direct_ arg,
+ * not an ordered arg. (In practice we're unlikely to get this far for
+ * hypotheticals, since make_fn_arguments would probably fail to unify
+ * types, but we can't change the order of these.)
*/
if (nargs > 0 && vatype == ANYOID && func_variadic)
{
- Oid va_arr_typid = actual_arg_types[nargs - 1];
+ int ignore_args = (agg_within_group ? list_length(agg_order) : 0);
+ Oid va_arr_typid = actual_arg_types[nargs - 1 - ignore_args];
+
+ if (ishypotheticalsetfunc)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("explicit VARIADIC argument not allowed for hypothetical set function"),
+ parser_errposition(pstate,
+ exprLocation((Node *) list_nth(fargs, nargs - 1 - ignore_args)))));
if (!OidIsValid(get_element_type(va_arr_typid)))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("VARIADIC argument must be an array"),
- parser_errposition(pstate, exprLocation((Node *) llast(fargs)))));
+ parser_errposition(pstate,
+ exprLocation((Node *) list_nth(fargs, nargs - 1 - ignore_args)))));
}
/* build the appropriate output structure */
@@ -421,6 +542,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
/* aggregate function */
Aggref *aggref = makeNode(Aggref);
+ if (agg_within_group && !isordsetfunc)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("%s is not an ordered set function",
+ func_signature_string(funcname, nargs, NIL, actual_arg_types))));
+
aggref->aggfnoid = funcid;
aggref->aggtype = rettype;
/* aggcollid and inputcollid will be set by parse_collate.c */
@@ -428,14 +555,24 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
aggref->aggfilter = agg_filter;
aggref->aggstar = agg_star;
aggref->aggvariadic = func_variadic;
+ aggref->ishypothetical = ishypotheticalsetfunc;
/* agglevelsup will be set by transformAggregateCall */
aggref->location = location;
+ if (isordsetfunc
+ && number_of_args >= 0
+ && number_of_args != list_length(fargs))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("incorrect number of direct arguments to ordered set function %s",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
+
/*
* Reject attempt to call a parameterless aggregate without (*)
* syntax. This is mere pedantry but some folks insisted ...
*/
- if (fargs == NIL && !agg_star)
+ if (fargs == NIL && !agg_star && !agg_within_group)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("%s(*) must be used to call a parameterless aggregate function",
@@ -464,7 +601,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
parser_errposition(pstate, location)));
/* parse_agg.c does additional aggregate-specific processing */
- transformAggregateCall(pstate, aggref, fargs, agg_order, agg_distinct);
+ transformAggregateCall(pstate, aggref, fargs, agg_order,
+ agg_distinct, agg_within_group);
retval = (Node *) aggref;
}
@@ -473,6 +611,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
/* window function */
WindowFunc *wfunc = makeNode(WindowFunc);
+ if (agg_within_group)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("WITHIN GROUP not allowed in window functions"),
+ parser_errposition(pstate, location)));
+
/*
* True window functions must be called with a window definition.
*/
@@ -1374,11 +1518,21 @@ func_get_detail(List *funcname,
void
make_fn_arguments(ParseState *pstate,
List *fargs,
+ List *agg_order,
Oid *actual_arg_types,
- Oid *declared_arg_types)
+ Oid *declared_arg_types,
+ bool requiresUnification)
{
ListCell *current_fargs;
+ ListCell *current_aoargs;
int i = 0;
+ int unify_offset = -1;
+
+ if (requiresUnification)
+ {
+ unify_offset = list_length(fargs) - list_length(agg_order);
+ Assert(unify_offset >= 0);
+ }
foreach(current_fargs, fargs)
{
@@ -1386,6 +1540,7 @@ make_fn_arguments(ParseState *pstate,
if (actual_arg_types[i] != declared_arg_types[i])
{
Node *node = (Node *) lfirst(current_fargs);
+ Node *temp = NULL;
/*
* If arg is a NamedArgExpr, coerce its input expr instead --- we
@@ -1406,18 +1561,66 @@ make_fn_arguments(ParseState *pstate,
}
else
{
- node = coerce_type(pstate,
- node,
- actual_arg_types[i],
- declared_arg_types[i], -1,
- COERCION_IMPLICIT,
- COERCE_IMPLICIT_CAST,
- -1);
- lfirst(current_fargs) = node;
+ /*
+ * If we are dealing with a hypothetical set function, we
+ * need to unify agg_order and fargs.
+ */
+
+ if (declared_arg_types[i] == ANYOID && requiresUnification)
+ {
+ Oid unification_oid;
+ SortBy *unify_with = (SortBy *) list_nth(agg_order,i - unify_offset);
+
+ unification_oid = select_common_type(pstate,
+ list_make2(unify_with->node,node),
+ "WITHIN GROUP",
+ NULL);
+
+ declared_arg_types[i + list_length(agg_order)] = unification_oid;
+
+ temp = coerce_type(pstate,
+ node,
+ actual_arg_types[i],
+ unification_oid, -1,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ }
+ else
+ {
+ temp = coerce_type(pstate,
+ node,
+ actual_arg_types[i],
+ declared_arg_types[i], -1,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ }
+
+ lfirst(current_fargs) = temp;
}
}
i++;
}
+
+ foreach(current_aoargs, agg_order)
+ {
+ if (actual_arg_types[i] != declared_arg_types[i])
+ {
+ SortBy *node = (SortBy *) lfirst(current_aoargs);
+ Node *temp = NULL;
+
+ temp = coerce_type(pstate,
+ node->node,
+ actual_arg_types[i],
+ declared_arg_types[i], -1,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ node->node = temp;
+ }
+ i++;
+ }
}
/*
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index dd80fa9..08cbabd 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -823,7 +823,7 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
false);
/* perform the necessary typecasting of arguments */
- make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
+ make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types, false);
/* and build the expression node */
result = makeNode(OpExpr);
@@ -953,7 +953,7 @@ make_scalar_array_op(ParseState *pstate, List *opname,
declared_arg_types[1] = res_atypeId;
/* perform the necessary typecasting of arguments */
- make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
+ make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types, false);
/* and build the expression node */
result = makeNode(ScalarArrayOpExpr);
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 41a8982..8ab553e 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -19,13 +19,13 @@ OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
array_userfuncs.o arrayutils.o bool.o \
cash.o char.o date.o datetime.o datum.o domains.o \
enum.o float.o format_type.o \
- geo_ops.o geo_selfuncs.o int.o int8.o json.o jsonfuncs.o like.o \
+ geo_ops.o geo_selfuncs.o hypotheticalset.o int.o int8.o json.o jsonfuncs.o like.o \
lockfuncs.o misc.o nabstime.o name.o numeric.o numutils.o \
oid.o oracle_compat.o pseudotypes.o rangetypes.o rangetypes_gist.o \
rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \
tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \
network.o mac.o inet_cidr_ntop.o inet_net_pton.o \
- ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \
+ inversedistribution.o ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \
ascii.o quote.o pgstatfuncs.o encode.o dbsize.o genfile.o trigfuncs.o \
tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
diff --git a/src/backend/utils/adt/hypotheticalset.c b/src/backend/utils/adt/hypotheticalset.c
new file mode 100644
index 0000000..98af805
--- /dev/null
+++ b/src/backend/utils/adt/hypotheticalset.c
@@ -0,0 +1,219 @@
+/*-------------------------------------------------------------------------
+ *
+ * hypotheticalset.c
+ * Hypothetical set functions.
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/hypotheticalset.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "fmgr.h"
+#include <string.h>
+#include <math.h>
+
+#include "utils/tuplesort.h"
+#include "catalog/pg_type.h"
+#include "utils/datetime.h"
+#include "utils/builtins.h"
+#include "executor/executor.h"
+
+Datum hypothetical_rank_final(PG_FUNCTION_ARGS);
+Datum hypothetical_dense_rank_final(PG_FUNCTION_ARGS);
+Datum hypothetical_percent_rank_final(PG_FUNCTION_ARGS);
+Datum hypothetical_cume_dist_final(PG_FUNCTION_ARGS);
+
+
+/*
+ * Common code to sanity-check args for hypothetical set functions. No need
+ * for friendly errors, these can only happen if someone's messing up the
+ * aggregate definitions. The checks are needed for security, however; but we
+ * only need them once per call site. Store a pointer to the tupdesc as a
+ * sentinel.
+ */
+
+static void
+hypothetical_check_argtypes(FunctionCallInfo fcinfo, int nargs, TupleDesc tupdesc)
+{
+ int i;
+
+ if (!tupdesc
+ || (nargs + 1) != tupdesc->natts
+ || tupdesc->attrs[nargs]->atttypid != BOOLOID)
+ elog(ERROR, "type mismatch in hypothetical set function");
+
+ for (i = 0; i < nargs; ++i)
+ if (get_fn_expr_argtype(fcinfo->flinfo,i) != tupdesc->attrs[i]->atttypid)
+ elog(ERROR, "type mismatch in hypothetical set function");
+
+ fcinfo->flinfo->fn_extra = tupdesc;
+}
+
+/*
+ * rank(float8) - rank of hypothetical row
+ */
+Datum
+hypothetical_rank_final(PG_FUNCTION_ARGS)
+{
+ Tuplesortstate *sorter = NULL;
+ TupleDesc tupdesc = NULL;
+ TupleTableSlot *slot = NULL;
+ Oid datumtype = InvalidOid;
+ int nargs = PG_NARGS();
+ int i;
+ int64 rank = 1;
+
+ AggSetGetSortInfo(fcinfo, &sorter, &tupdesc, &slot, &datumtype);
+
+ if (fcinfo->flinfo->fn_extra == NULL
+ || fcinfo->flinfo->fn_extra != tupdesc)
+ hypothetical_check_argtypes(fcinfo, nargs, tupdesc);
+
+ /* insert the hypothetical row into the sort */
+
+ ExecClearTuple(slot);
+ for (i = 0; i < nargs; ++i)
+ {
+ slot->tts_values[i] = PG_GETARG_DATUM(i);
+ slot->tts_isnull[i] = PG_ARGISNULL(i);
+ }
+ slot->tts_values[nargs] = BoolGetDatum(true);
+ slot->tts_isnull[nargs] = false;
+ ExecStoreVirtualTuple(slot);
+
+ tuplesort_puttupleslot(sorter, slot);
+
+ tuplesort_performsort(sorter);
+
+ while (tuplesort_gettupleslot(sorter, true, slot))
+ {
+ bool isnull;
+ Datum d = slot_getattr(slot, nargs + 1, &isnull);
+
+ if (!isnull && DatumGetBool(d))
+ break;
+
+ ++rank;
+ }
+
+ ExecClearTuple(slot);
+
+ PG_RETURN_INT64(rank);
+}
+
+/*
+ * dense_rank(float8) - rank of hypothetical row
+ * without gap in ranking
+ */
+Datum
+hypothetical_dense_rank_final(PG_FUNCTION_ARGS)
+{
+ Tuplesortstate *sorter = NULL;
+ TupleDesc tupdesc = NULL;
+ TupleTableSlot *slot = NULL;
+ Oid datumtype = InvalidOid;
+ int nargs = PG_NARGS();
+ int i;
+ int64 rank = 1;
+ int duplicate_count = 0;
+ TupleTableSlot *slot2 = NULL;
+ AttrNumber *colidx;
+ FmgrInfo *equalfns;
+ int numDistinctCol = 0;
+ MemoryContext memcontext;
+
+ AggSetGetSortInfo(fcinfo, &sorter, &tupdesc, &slot, &datumtype);
+
+ if (fcinfo->flinfo->fn_extra == NULL
+ || fcinfo->flinfo->fn_extra != tupdesc)
+ hypothetical_check_argtypes(fcinfo, nargs, tupdesc);
+
+ /* insert the hypothetical row into the sort */
+
+ ExecClearTuple(slot);
+ for (i = 0; i < nargs; ++i)
+ {
+ slot->tts_values[i] = PG_GETARG_DATUM(i);
+ slot->tts_isnull[i] = PG_ARGISNULL(i);
+ }
+ slot->tts_values[nargs] = BoolGetDatum(true);
+ slot->tts_isnull[nargs] = false;
+ ExecStoreVirtualTuple(slot);
+
+ tuplesort_puttupleslot(sorter, slot);
+
+ tuplesort_performsort(sorter);
+
+ numDistinctCol = AggSetGetDistinctInfo(fcinfo, &slot2, &colidx, &equalfns);
+
+ ExecClearTuple(slot2);
+
+ AggSetGetPerTupleContext(fcinfo, &memcontext);
+
+ while (tuplesort_gettupleslot(sorter, true, slot))
+ {
+ TupleTableSlot *tmpslot = slot2;
+ bool isnull;
+ Datum d = slot_getattr(slot, nargs + 1, &isnull);
+
+ if (!isnull && DatumGetBool(d))
+ break;
+
+ if (!TupIsNull(slot2)
+ && execTuplesMatch(slot, slot2,
+ (numDistinctCol - 1),
+ colidx,
+ equalfns,
+ memcontext))
+ ++duplicate_count;
+
+ slot2 = slot;
+ slot = tmpslot;
+
+ ++rank;
+ }
+
+ ExecClearTuple(slot);
+ ExecClearTuple(slot2);
+
+ rank = rank - duplicate_count;
+ PG_RETURN_INT64(rank);
+}
+
+/* percent_rank(float8)
+ * Calculates the relative ranking of hypothetical
+ * row within a group
+ */
+
+Datum
+hypothetical_percent_rank_final(PG_FUNCTION_ARGS)
+{
+ Datum rank = hypothetical_rank_final(fcinfo);
+ int64 rank_val = DatumGetInt64(rank);
+ int64 rowcount = AggSetGetRowCount(fcinfo) + 1;
+
+ float8 result_val = (float8) (rank_val - 1) / (float8) (rowcount - 1);
+
+ PG_RETURN_FLOAT8(result_val);
+}
+
+/* cume_dist - cumulative distribution of hypothetical
+ * row in a group
+ */
+
+Datum
+hypothetical_cume_dist_final(PG_FUNCTION_ARGS)
+{
+ Datum rank = hypothetical_rank_final(fcinfo);
+ int64 rank_val = DatumGetInt64(rank);
+ int64 rowcount = AggSetGetRowCount(fcinfo) + 1;
+
+ float8 result_val = (float8) (rank_val) / (float8) (rowcount);
+
+ PG_RETURN_FLOAT8(result_val);
+}
diff --git a/src/backend/utils/adt/inversedistribution.c b/src/backend/utils/adt/inversedistribution.c
new file mode 100644
index 0000000..fdda722
--- /dev/null
+++ b/src/backend/utils/adt/inversedistribution.c
@@ -0,0 +1,662 @@
+/*-------------------------------------------------------------------------
+ *
+ * inversedistribution.c
+ * Inverse distribution functions.
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/inversedistribution.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "fmgr.h"
+#include <string.h>
+#include <math.h>
+
+#include "utils/tuplesort.h"
+#include "catalog/pg_type.h"
+#include "utils/datetime.h"
+#include "utils/lsyscache.h"
+#include "utils/array.h"
+
+/*
+ * percentile_disc(float8) - discrete percentile
+ */
+
+Datum percentile_disc_final(PG_FUNCTION_ARGS);
+
+Datum
+percentile_disc_final(PG_FUNCTION_ARGS)
+{
+ float8 percentile;
+ Tuplesortstate *sorter;
+ Oid datumtype;
+ Datum val;
+ bool isnull;
+ int64 skiprows;
+ int64 rowcount = AggSetGetRowCount(fcinfo);
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+
+ percentile = PG_GETARG_FLOAT8(0);
+
+ AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+
+ if (percentile < 0 || percentile > 1 || isnan(percentile))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("percentile value %g must be between 0 and 1", percentile)));
+
+ if (rowcount < 1)
+ PG_RETURN_NULL();
+
+ tuplesort_performsort(sorter);
+
+ /*
+ * We need the smallest K such that (K/N) >= percentile. K starts at 1.
+ * Therefore K >= N*percentile
+ * Therefore K = ceil(N*percentile)
+ * So we skip K-1 rows (if K>0) and return the next row fetched.
+ *
+ * We don't actually expect to see nulls in the input, our strict flag
+ * should have filtered them out, but we're required to not crash if
+ * there is one.
+ */
+
+ skiprows = (int64) ceil(percentile * rowcount);
+ Assert(skiprows <= rowcount);
+
+ while (--skiprows > 0)
+ if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ elog(ERROR,"missing row in percentile_disc");
+
+ if (!tuplesort_getdatum(sorter, true, &val, &isnull))
+ elog(ERROR,"missing row in percentile_disc");
+
+ if (isnull)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_DATUM(val);
+}
+
+
+/*
+ * For percentile_cont, we need a way to interpolate between consecutive
+ * values. Use a helper function for that, so that we can share the rest
+ * of the code between types.
+ */
+
+static Datum float8_lerp(Datum lo, Datum hi, float8 pct)
+{
+ float8 loval = DatumGetFloat8(lo);
+ float8 hival = DatumGetFloat8(hi);
+ return Float8GetDatum(loval + (pct * (hival - loval)));
+}
+
+static Datum interval_lerp(Datum lo, Datum hi, float8 pct)
+{
+ Datum diff_result = DirectFunctionCall2(interval_mi, hi, lo);
+ Datum mul_result = DirectFunctionCall2(interval_mul,
+ diff_result,
+ Float8GetDatumFast(pct));
+ return DirectFunctionCall2(interval_pl, mul_result, lo);
+}
+
+typedef Datum (*LerpFunc)(Datum lo, Datum hi, float8 pct);
+
+static Datum
+percentile_cont_final_common(FunctionCallInfo fcinfo,
+ Oid expect_type,
+ LerpFunc lerpfunc)
+{
+ float8 percentile;
+ int64 rowcount = AggSetGetRowCount(fcinfo);
+ Tuplesortstate *sorter;
+ Oid datumtype;
+ Datum val;
+ Datum first_row;
+ Datum second_row;
+ float8 proportion;
+ bool isnull;
+ int64 skiprows;
+ int64 lower_row = 0;
+ int64 higher_row = 0;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+
+ percentile = PG_GETARG_FLOAT8(0);
+
+ AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+
+ Assert(datumtype == expect_type);
+
+ if (percentile < 0 || percentile > 1 || isnan(percentile))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("percentile value %g must be between 0 and 1", percentile)));
+
+ if (rowcount < 1)
+ PG_RETURN_NULL();
+
+ tuplesort_performsort(sorter);
+
+ lower_row = floor(percentile * (rowcount - 1));
+ higher_row = ceil(percentile * (rowcount - 1));
+
+ Assert(lower_row < rowcount);
+
+ for (skiprows = lower_row; skiprows > 0; --skiprows)
+ if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ elog(ERROR,"missing row in percentile_cont");
+
+ if (!tuplesort_getdatum(sorter, true, &first_row, &isnull))
+ elog(ERROR,"missing row in percentile_cont");
+ if (isnull)
+ PG_RETURN_NULL();
+
+ if (lower_row == higher_row)
+ {
+ val = first_row;
+ }
+ else
+ {
+ if (!tuplesort_getdatum(sorter, true, &second_row, &isnull))
+ elog(ERROR,"missing row in percentile_cont");
+
+ if (isnull)
+ PG_RETURN_NULL();
+
+ proportion = (percentile * (rowcount-1)) - lower_row;
+ val = lerpfunc(first_row, second_row, proportion);
+ }
+
+ if (isnull)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_DATUM(val);
+}
+
+
+
+/*
+ * percentile_cont(float8) - continuous percentile
+ */
+
+Datum percentile_cont_float8_final(PG_FUNCTION_ARGS);
+Datum percentile_cont_interval_final(PG_FUNCTION_ARGS);
+
+Datum
+percentile_cont_float8_final(PG_FUNCTION_ARGS)
+{
+ return percentile_cont_final_common(fcinfo, FLOAT8OID, float8_lerp);
+}
+
+/*
+ * percentile_interval_cont(Interval) - continuous percentile for Interval
+ */
+
+Datum
+percentile_cont_interval_final(PG_FUNCTION_ARGS)
+{
+ return percentile_cont_final_common(fcinfo, INTERVALOID, interval_lerp);
+}
+
+
+/*
+ * mode() - most common value
+ */
+
+Datum mode_final(PG_FUNCTION_ARGS);
+
+Datum
+mode_final(PG_FUNCTION_ARGS)
+{
+ Tuplesortstate *sorter;
+ Oid datumtype;
+ bool isnull;
+ Datum val;
+ Datum last_val = (Datum) 0;
+ bool last_val_is_mode = false;
+ int64 val_freq = 0;
+ Datum mode_val = (Datum) 0;
+ int64 mode_freq = 0;
+ FmgrInfo *equalfn;
+ bool shouldfree;
+
+ struct mode_type_info {
+ Oid typid;
+ int16 typLen;
+ bool typByVal;
+ } *typinfo = fcinfo->flinfo->fn_extra;
+
+ AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ AggSetGetDistinctInfo(fcinfo, NULL, NULL, &equalfn);
+
+ if (!typinfo || typinfo->typid != datumtype)
+ {
+ if (typinfo)
+ pfree(typinfo);
+ typinfo = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ sizeof(struct mode_type_info));
+ typinfo->typid = datumtype;
+ get_typlenbyval(datumtype, &typinfo->typLen, &typinfo->typByVal);
+ }
+
+ shouldfree = !(typinfo->typByVal);
+
+ tuplesort_performsort(sorter);
+
+ while (tuplesort_getdatum(sorter, true, &val, &isnull))
+ {
+ if (isnull)
+ continue;
+
+ if (val_freq == 0)
+ {
+ /* first value - assume modal until shown otherwise */
+ mode_val = last_val = val;
+ mode_freq = val_freq = 1;
+ last_val_is_mode = true;
+ }
+ else if (DatumGetBool(FunctionCall2(equalfn, val, last_val)))
+ {
+ /* value equal to previous value */
+ if (last_val_is_mode)
+ ++mode_freq;
+ else if (++val_freq > mode_freq)
+ {
+ if (shouldfree)
+ {
+ pfree(DatumGetPointer(mode_val));
+ pfree(DatumGetPointer(val));
+ }
+
+ mode_val = last_val;
+ mode_freq = val_freq;
+ last_val_is_mode = true;
+ }
+ else if (shouldfree)
+ pfree(DatumGetPointer(val));
+ }
+ else
+ {
+ if (shouldfree && !last_val_is_mode)
+ pfree(DatumGetPointer(last_val));
+
+ last_val_is_mode = false;
+ last_val = val;
+ val_freq = 1;
+ }
+ }
+
+ if (shouldfree && !last_val_is_mode)
+ pfree(DatumGetPointer(last_val));
+
+ if (mode_freq)
+ PG_RETURN_DATUM(mode_val);
+ else
+ PG_RETURN_NULL();
+}
+
+
+
+/*
+ * percentile_disc(float8[]) - discrete percentiles
+ */
+
+Datum percentile_disc_multi_final(PG_FUNCTION_ARGS);
+
+struct pct_info {
+ int64 first_row;
+ int64 second_row;
+ float8 proportion;
+ int idx;
+};
+
+static int pct_info_cmp(const void *pa, const void *pb)
+{
+ const struct pct_info *a = pa;
+ const struct pct_info *b = pb;
+ if (a->first_row == b->first_row)
+ return (a->second_row < b->second_row) ? -1 : (a->second_row == b->second_row) ? 0 : 1;
+ else
+ return (a->first_row < b->first_row) ? -1 : 1;
+}
+
+static struct pct_info *setup_pct_info(int num_percentiles,
+ Datum *percentiles_datum,
+ bool *percentiles_null,
+ int64 rowcount,
+ bool continuous)
+{
+ struct pct_info *pct_info = palloc(num_percentiles * sizeof(struct pct_info));
+ int i;
+
+ for (i = 0; i < num_percentiles; i++)
+ {
+ pct_info[i].idx = i;
+
+ if (percentiles_null[i])
+ {
+ pct_info[i].first_row = 0;
+ pct_info[i].second_row = 0;
+ pct_info[i].proportion = 0;
+ }
+ else
+ {
+ float8 p = DatumGetFloat8(percentiles_datum[i]);
+
+ if (p < 0 || p > 1 || isnan(p))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("percentile value %g must be between 0 and 1", p)));
+
+ if (continuous)
+ {
+ pct_info[i].first_row = 1 + floor(p * (rowcount - 1));
+ pct_info[i].second_row = 1 + ceil(p * (rowcount - 1));
+ pct_info[i].proportion = (p * (rowcount-1)) - floor(p * (rowcount-1));
+ }
+ else
+ {
+ /*
+ * We need the smallest K such that (K/N) >= percentile. K starts at 1.
+ * Therefore K >= N*percentile
+ * Therefore K = ceil(N*percentile), minimum 1
+ */
+
+ pct_info[i].first_row = Max(1, (int64) ceil(rowcount * p));
+ pct_info[i].second_row = 0;
+ pct_info[i].proportion = 0;
+ }
+ }
+ }
+
+ qsort(pct_info, num_percentiles, sizeof(struct pct_info), pct_info_cmp);
+
+ return pct_info;
+}
+
+Datum
+percentile_disc_multi_final(PG_FUNCTION_ARGS)
+{
+ ArrayType *param;
+ Datum *percentiles_datum;
+ bool *percentiles_null;
+ int num_percentiles;
+ int64 rowcount = AggSetGetRowCount(fcinfo);
+ int64 rownum = 0;
+ Tuplesortstate *sorter;
+ Oid datumtype;
+ Datum val;
+ bool isnull;
+ Datum *result_datum;
+ bool *result_isnull;
+ int i;
+ struct pct_info *pct_info;
+
+ struct mode_type_info {
+ Oid typid;
+ int16 typLen;
+ bool typByVal;
+ char typAlign;
+ } *typinfo = fcinfo->flinfo->fn_extra;
+
+ if (PG_ARGISNULL(0) || rowcount < 1)
+ PG_RETURN_NULL();
+
+ AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+
+ if (!typinfo || typinfo->typid != datumtype)
+ {
+ if (typinfo)
+ pfree(typinfo);
+ typinfo = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ sizeof(struct mode_type_info));
+ typinfo->typid = datumtype;
+ get_typlenbyvalalign(datumtype,
+ &typinfo->typLen,
+ &typinfo->typByVal,
+ &typinfo->typAlign);
+ }
+
+ param = PG_GETARG_ARRAYTYPE_P(0);
+
+ deconstruct_array(param, FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd',
+ &percentiles_datum, &percentiles_null, &num_percentiles);
+
+ if (num_percentiles == 0)
+ PG_RETURN_POINTER(construct_empty_array(datumtype));
+
+ result_datum = palloc0(num_percentiles * sizeof(Datum));
+ result_isnull = palloc0(num_percentiles * sizeof(bool));
+
+ pct_info = setup_pct_info(num_percentiles,
+ percentiles_datum,
+ percentiles_null,
+ rowcount,
+ false);
+
+ /*
+ * Start by dealing with any nulls in the param array - those are
+ * sorted to the front on row=0, so set the corresponding result
+ * indexes to null
+ */
+ for (i = 0; i < num_percentiles; ++i)
+ {
+ int idx = pct_info[i].idx;
+
+ if (pct_info[i].first_row > 0)
+ break;
+
+ result_datum[idx] = (Datum) 0;
+ result_isnull[idx] = true;
+ }
+
+ /*
+ * If there's anything left after doing the nulls, then grind the
+ * input and extract the needed values
+ */
+ if (i < num_percentiles)
+ {
+ tuplesort_performsort(sorter);
+
+ for (; i < num_percentiles; ++i)
+ {
+ int64 target_row = pct_info[i].first_row;
+ int idx = pct_info[i].idx;
+
+ if (target_row > rownum)
+ {
+ while (target_row > ++rownum)
+ {
+ if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ elog(ERROR,"missing row in percentile_disc");
+ }
+
+ if (!tuplesort_getdatum(sorter, true, &val, &isnull))
+ elog(ERROR,"missing row in percentile_disc");
+ }
+
+ result_datum[idx] = val;
+ result_isnull[idx] = isnull;
+ }
+ }
+
+ /* We make the output array the same shape as the input */
+
+ PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull,
+ ARR_NDIM(param),
+ ARR_DIMS(param), ARR_LBOUND(param),
+ datumtype,
+ typinfo->typLen,
+ typinfo->typByVal,
+ typinfo->typAlign));
+}
+
+static Datum
+percentile_cont_multi_final_common(FunctionCallInfo fcinfo,
+ Oid expect_type,
+ int16 typLen, bool typByVal, char typAlign,
+ LerpFunc lerpfunc)
+{
+ ArrayType *param;
+ Datum *percentiles_datum;
+ bool *percentiles_null;
+ int num_percentiles;
+ int64 rowcount = AggSetGetRowCount(fcinfo);
+ int64 rownum = 0;
+ int64 rownum_second = 0;
+ Tuplesortstate *sorter;
+ Oid datumtype;
+ Datum first_val;
+ Datum second_val;
+ bool isnull;
+ Datum *result_datum;
+ bool *result_isnull;
+ int i;
+ struct pct_info *pct_info;
+
+ if (PG_ARGISNULL(0) || rowcount < 1)
+ PG_RETURN_NULL();
+
+ AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ Assert(datumtype == expect_type);
+
+ param = PG_GETARG_ARRAYTYPE_P(0);
+
+ deconstruct_array(param, FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd',
+ &percentiles_datum, &percentiles_null, &num_percentiles);
+
+ if (num_percentiles == 0)
+ PG_RETURN_POINTER(construct_empty_array(datumtype));
+
+ result_datum = palloc0(num_percentiles * sizeof(Datum));
+ result_isnull = palloc0(num_percentiles * sizeof(bool));
+
+ pct_info = setup_pct_info(num_percentiles,
+ percentiles_datum,
+ percentiles_null,
+ rowcount,
+ true);
+
+ /*
+ * Start by dealing with any nulls in the param array - those are
+ * sorted to the front on row=0, so set the corresponding result
+ * indexes to null
+ */
+ for (i = 0; i < num_percentiles; ++i)
+ {
+ int idx = pct_info[i].idx;
+
+ if (pct_info[i].first_row > 0)
+ break;
+
+ result_datum[idx] = (Datum) 0;
+ result_isnull[idx] = true;
+ }
+
+ /*
+ * If there's anything left after doing the nulls, then grind the
+ * input and extract the needed values
+ */
+ if (i < num_percentiles)
+ {
+ tuplesort_performsort(sorter);
+
+ for (; i < num_percentiles; ++i)
+ {
+ int64 target_row = pct_info[i].first_row;
+ bool need_lerp = pct_info[i].second_row > target_row;
+ int idx = pct_info[i].idx;
+
+ if (target_row > rownum_second)
+ {
+ rownum = rownum_second;
+
+ while (target_row > ++rownum)
+ {
+ if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ elog(ERROR,"missing row in percentile_cont");
+ }
+
+ if (!tuplesort_getdatum(sorter, true, &first_val, &isnull) || isnull)
+ elog(ERROR,"missing row in percentile_cont");
+
+ rownum_second = rownum;
+
+ if (need_lerp)
+ {
+ if (!tuplesort_getdatum(sorter, true, &second_val, &isnull) || isnull)
+ elog(ERROR,"missing row in percentile_cont");
+ ++rownum_second;
+ }
+ }
+ else if (target_row == rownum_second)
+ {
+ first_val = second_val;
+ rownum = rownum_second;
+
+ if (need_lerp)
+ {
+ if (!tuplesort_getdatum(sorter, true, &second_val, &isnull) || isnull)
+ elog(ERROR,"missing row in percentile_cont");
+ ++rownum_second;
+ }
+ }
+
+ if (need_lerp)
+ {
+ result_datum[idx] = lerpfunc(first_val, second_val, pct_info[i].proportion);
+ }
+ else
+ result_datum[idx] = first_val;
+
+ result_isnull[idx] = false;
+ }
+ }
+
+ /* We make the output array the same shape as the input */
+
+ PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull,
+ ARR_NDIM(param),
+ ARR_DIMS(param), ARR_LBOUND(param),
+ expect_type,
+ typLen,
+ typByVal,
+ typAlign));
+}
+
+
+/*
+ * percentile_cont(float8[]) within group (float8) - continuous percentiles
+ */
+
+Datum percentile_cont_float8_multi_final(PG_FUNCTION_ARGS);
+Datum percentile_cont_interval_multi_final(PG_FUNCTION_ARGS);
+
+Datum
+percentile_cont_float8_multi_final(PG_FUNCTION_ARGS)
+{
+ return percentile_cont_multi_final_common(fcinfo,
+ FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd',
+ float8_lerp);
+}
+
+/*
+ * percentile_cont(float8[]) within group (Interval) - continuous percentiles
+ */
+
+Datum
+percentile_cont_interval_multi_final(PG_FUNCTION_ARGS)
+{
+ return percentile_cont_multi_final_common(fcinfo,
+ INTERVALOID, 16, false, 'd',
+ interval_lerp);
+}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 04b1c4f..885d7a8 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -22,6 +22,7 @@
#include "access/sysattr.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
+#include "catalog/pg_aggregate.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -293,6 +294,9 @@ static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
static int print_function_arguments(StringInfo buf, HeapTuple proctup,
bool print_table_args, bool print_defaults);
static void print_function_rettype(StringInfo buf, HeapTuple proctup);
+static void print_aggregate_arguments(StringInfo buf,
+ HeapTuple proctup, HeapTuple aggtup,
+ bool print_defaults);
static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
Bitmapset *rels_used);
static bool refname_is_unique(char *refname, deparse_namespace *dpns,
@@ -402,6 +406,8 @@ static char *generate_function_name(Oid funcid, int nargs,
static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
static text *string_to_text(char *str);
static char *flatten_reloptions(Oid relid);
+static void get_aggstd_expr(Aggref *aggref, deparse_context *context);
+static void get_ordset_expr(Aggref *aggref, deparse_context *context);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
@@ -2267,6 +2273,149 @@ print_function_arguments(StringInfo buf, HeapTuple proctup,
/*
+ * pg_get_aggregate_arguments
+ * Get a nicely-formatted list of arguments for an aggregate.
+ * This is everything that would go after the function name
+ * in CREATE AGGREGATE, _including_ the parens, because in the
+ * case of ordered set funcs, we emit the WITHIN GROUP clause
+ * too.
+ */
+Datum
+pg_get_aggregate_arguments(PG_FUNCTION_ARGS)
+{
+ Oid funcid = PG_GETARG_OID(0);
+ StringInfoData buf;
+ HeapTuple proctup;
+ HeapTuple aggtup;
+
+ initStringInfo(&buf);
+
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(proctup))
+ elog(ERROR, "cache lookup failed for function %u", funcid);
+
+ aggtup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(aggtup))
+ elog(ERROR, "function %u is not an aggregate function", funcid);
+
+ (void) print_aggregate_arguments(&buf, proctup, aggtup, true);
+
+ ReleaseSysCache(aggtup);
+ ReleaseSysCache(proctup);
+
+ PG_RETURN_TEXT_P(string_to_text(buf.data));
+}
+
+/*
+ * pg_get_aggregate_identity_arguments
+ * Get a formatted list of arguments for an aggregate.
+ * This is everything that would go after the function name in
+ * ALTER AGGREGATE, etc. In particular, don't print defaults.
+ * Currently, this is identical to pg_get_aggregate_arguments,
+ * but if we ever allow defaults that will change.
+ */
+Datum
+pg_get_aggregate_identity_arguments(PG_FUNCTION_ARGS)
+{
+ Oid funcid = PG_GETARG_OID(0);
+ StringInfoData buf;
+ HeapTuple proctup;
+ HeapTuple aggtup;
+
+ initStringInfo(&buf);
+
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(proctup))
+ elog(ERROR, "cache lookup failed for function %u", funcid);
+
+ aggtup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(aggtup))
+ elog(ERROR, "function %u is not an aggregate function", funcid);
+
+ (void) print_aggregate_arguments(&buf, proctup, aggtup, false);
+
+ ReleaseSysCache(aggtup);
+ ReleaseSysCache(proctup);
+
+ PG_RETURN_TEXT_P(string_to_text(buf.data));
+}
+
+
+/*
+ * Common code for pg_get_aggregate_arguments
+ * We print argument defaults only if print_defaults is true.
+ */
+static void
+print_aggregate_arguments(StringInfo buf,
+ HeapTuple proctup, HeapTuple aggtup,
+ bool print_defaults)
+{
+ Form_pg_aggregate agg = (Form_pg_aggregate) GETSTRUCT(aggtup);
+ int numargs;
+ bool ordsetfunc = agg->aggisordsetfunc;
+ int numdirectargs = agg->aggordnargs;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ int i;
+
+ /* defaults not supported at this time */
+ (void) print_defaults;
+
+ numargs = get_func_arg_info(proctup,
+ &argtypes, &argnames, &argmodes);
+
+ appendStringInfoChar(buf, '(');
+
+ for (i = 0; i < numargs; i++)
+ {
+ Oid argtype = argtypes[i];
+ char *argname = argnames ? argnames[i] : NULL;
+ char argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
+ const char *modename;
+
+ switch (argmode)
+ {
+ case PROARGMODE_IN:
+ modename = "";
+ break;
+ case PROARGMODE_VARIADIC:
+ modename = "VARIADIC ";
+ break;
+ default:
+ elog(ERROR, "invalid parameter mode '%c'", argmode);
+ modename = NULL; /* keep compiler quiet */
+ break;
+ }
+
+ if (i == numdirectargs)
+ {
+ appendStringInfoString(buf, ") WITHIN GROUP (");
+ }
+ else if (i > 0)
+ appendStringInfoString(buf, ", ");
+
+ appendStringInfoString(buf, modename);
+
+ if (argname && argname[0])
+ appendStringInfo(buf, "%s ", quote_identifier(argname));
+
+ appendStringInfoString(buf, format_type_be(argtype));
+ }
+
+ if (ordsetfunc)
+ {
+ if (numdirectargs < 0 || numdirectargs == numargs)
+ appendStringInfoString(buf, ") WITHIN GROUP (*");
+ }
+ else if (numargs == 0)
+ appendStringInfoChar(buf, '*');
+
+ appendStringInfoChar(buf, ')');
+}
+
+
+/*
* deparse_expression - General utility for deparsing expressions
*
* calls deparse_expression_pretty with all prettyPrinting disabled
@@ -7388,6 +7537,80 @@ static void
get_agg_expr(Aggref *aggref, deparse_context *context)
{
StringInfo buf = context->buf;
+
+ if (aggref->isordset)
+ {
+ get_ordset_expr(aggref, context);
+ }
+ else
+ {
+ get_aggstd_expr(aggref, context);
+ }
+
+ if (aggref->aggfilter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)aggref->aggfilter, context, false);
+ }
+
+ appendStringInfoString(buf, ")");
+}
+
+static void
+get_ordset_expr(Aggref *aggref, deparse_context *context)
+{
+ StringInfo buf = context->buf;
+ Oid argtypes[FUNC_MAX_ARGS];
+ List *arglist;
+ int nargs;
+ ListCell *l;
+
+ arglist = NIL;
+ nargs = 0;
+
+ foreach(l, aggref->orddirectargs)
+ {
+ Node *arg = (Node *) lfirst(l);
+
+ Assert(!IsA(arg, NamedArgExpr));
+ if (nargs >= FUNC_MAX_ARGS) /* paranoia */
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ errmsg("too many arguments")));
+ argtypes[nargs] = exprType(arg);
+ nargs++;
+ }
+
+ /* For direct arguments in case of ordered set functions */
+ foreach(l, aggref->args)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+ Node *arg = (Node *) tle->expr;
+
+ Assert(!IsA(arg, NamedArgExpr));
+ if (nargs >= FUNC_MAX_ARGS) /* paranoia */
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ errmsg("too many arguments")));
+ argtypes[nargs] = exprType(arg);
+ arglist = lappend(arglist, arg);
+ nargs++;
+ }
+
+ appendStringInfo(buf, "%s(",
+ generate_function_name(aggref->aggfnoid, nargs,
+ NIL, argtypes,
+ false, NULL));
+
+ get_rule_expr((Node *)aggref->orddirectargs, context, true);
+ appendStringInfoString(buf, ") WITHIN GROUP (ORDER BY ");
+ get_rule_orderby(aggref->aggorder, aggref->args, false, context);
+
+}
+static void
+get_aggstd_expr(Aggref *aggref, deparse_context *context)
+{
+ StringInfo buf = context->buf;
Oid argtypes[FUNC_MAX_ARGS];
List *arglist;
int nargs;
@@ -7442,14 +7665,6 @@ get_agg_expr(Aggref *aggref, deparse_context *context)
appendStringInfoString(buf, " ORDER BY ");
get_rule_orderby(aggref->aggorder, aggref->args, false, context);
}
-
- if (aggref->aggfilter != NULL)
- {
- appendStringInfoString(buf, ") FILTER (WHERE ");
- get_rule_expr((Node *) aggref->aggfilter, context, false);
- }
-
- appendStringInfoChar(buf, ')');
}
/*
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index ea8af9f..7375d8e 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -1411,23 +1411,29 @@ tuplesort_performsort(Tuplesortstate *state)
* Internal routine to fetch the next tuple in either forward or back
* direction into *stup. Returns FALSE if no more tuples.
* If *should_free is set, the caller must pfree stup.tuple when done with it.
+ * stup may be null to move without fetching.
*/
static bool
tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
SortTuple *stup, bool *should_free)
{
unsigned int tuplen;
+ SortTuple dummy;
+ SortTuple *ptup = stup ? stup : &dummy;
switch (state->status)
{
case TSS_SORTEDINMEM:
Assert(forward || state->randomAccess);
- *should_free = false;
+ if (should_free)
+ *should_free = false;
if (forward)
{
if (state->current < state->memtupcount)
{
- *stup = state->memtuples[state->current++];
+ if (stup)
+ *stup = state->memtuples[state->current];
+ state->current++;
return true;
}
state->eof_reached = true;
@@ -1459,21 +1465,25 @@ tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
if (state->current <= 0)
return false;
}
- *stup = state->memtuples[state->current - 1];
+ if (stup)
+ *stup = state->memtuples[state->current - 1];
return true;
}
break;
case TSS_SORTEDONTAPE:
Assert(forward || state->randomAccess);
- *should_free = true;
+ if (should_free)
+ *should_free = true;
if (forward)
{
if (state->eof_reached)
return false;
if ((tuplen = getlen(state, state->result_tape, true)) != 0)
{
- READTUP(state, stup, state->result_tape, tuplen);
+ READTUP(state, ptup, state->result_tape, tuplen);
+ if (!stup && dummy.tuple)
+ pfree(dummy.tuple);
return true;
}
else
@@ -1546,12 +1556,15 @@ tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
state->result_tape,
tuplen))
elog(ERROR, "bogus tuple length in backward scan");
- READTUP(state, stup, state->result_tape, tuplen);
+ READTUP(state, ptup, state->result_tape, tuplen);
+ if (!stup && dummy.tuple)
+ pfree(dummy.tuple);
return true;
case TSS_FINALMERGE:
Assert(forward);
- *should_free = true;
+ if (should_free)
+ *should_free = true;
/*
* This code should match the inner loop of mergeonerun().
@@ -1563,11 +1576,11 @@ tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
int tupIndex;
SortTuple *newtup;
- *stup = state->memtuples[0];
+ *ptup = state->memtuples[0];
/* returned tuple is no longer counted in our memory space */
- if (stup->tuple)
+ if (ptup->tuple)
{
- tuplen = GetMemoryChunkSpace(stup->tuple);
+ tuplen = GetMemoryChunkSpace(ptup->tuple);
state->availMem += tuplen;
state->mergeavailmem[srcTape] += tuplen;
}
@@ -1598,6 +1611,8 @@ tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
newtup->tupindex = state->mergefreelist;
state->mergefreelist = tupIndex;
state->mergeavailslots[srcTape]++;
+ if (!stup && dummy.tuple)
+ pfree(dummy.tuple);
return true;
}
return false;
@@ -1620,20 +1635,22 @@ tuplesort_gettupleslot(Tuplesortstate *state, bool forward,
MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext);
SortTuple stup;
bool should_free;
+ bool found;
- if (!tuplesort_gettuple_common(state, forward, &stup, &should_free))
- stup.tuple = NULL;
+ found = tuplesort_gettuple_common(state, forward, (slot ? &stup : NULL), &should_free);
MemoryContextSwitchTo(oldcontext);
- if (stup.tuple)
+ if (found)
{
- ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free);
+ if (slot)
+ ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free);
return true;
}
else
{
- ExecClearTuple(slot);
+ if (slot)
+ ExecClearTuple(slot);
return false;
}
}
@@ -1692,24 +1709,27 @@ tuplesort_getdatum(Tuplesortstate *state, bool forward,
SortTuple stup;
bool should_free;
- if (!tuplesort_gettuple_common(state, forward, &stup, &should_free))
+ if (!tuplesort_gettuple_common(state, forward, (val ? &stup : NULL), &should_free))
{
MemoryContextSwitchTo(oldcontext);
return false;
}
- if (stup.isnull1 || state->datumTypeByVal)
+ if (val)
{
- *val = stup.datum1;
- *isNull = stup.isnull1;
- }
- else
- {
- if (should_free)
+ if (stup.isnull1 || state->datumTypeByVal)
+ {
*val = stup.datum1;
+ *isNull = stup.isnull1;
+ }
else
- *val = datumCopy(stup.datum1, false, state->datumTypeLen);
- *isNull = false;
+ {
+ if (should_free)
+ *val = stup.datum1;
+ else
+ *val = datumCopy(stup.datum1, false, state->datumTypeLen);
+ *isNull = false;
+ }
}
MemoryContextSwitchTo(oldcontext);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index bc70dd6..8e378bd 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -229,6 +229,7 @@ static void getTableData(TableInfo *tblinfo, int numTables, bool oids);
static void makeTableDataInfo(TableInfo *tbinfo, bool oids);
static void buildMatViewRefreshDependencies(Archive *fout);
static void getTableDataFKConstraints(void);
+static char *format_aggregate_arguments(FuncInfo *finfo, char *funcargs);
static char *format_function_arguments(FuncInfo *finfo, char *funcargs,
bool is_agg);
static char *format_function_arguments_old(Archive *fout,
@@ -9363,6 +9364,22 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
}
/*
+ * format_aggregate_arguments: generate function name and argument list
+ *
+ * This is used when we can rely on pg_get_aggregate_arguments to format
+ * the argument list.
+ */
+static char *
+format_aggregate_arguments(FuncInfo *finfo, char *funcargs)
+{
+ PQExpBufferData fn;
+
+ initPQExpBuffer(&fn);
+ appendPQExpBuffer(&fn, "%s%s", fmtId(finfo->dobj.name), funcargs);
+ return fn.data;
+}
+
+/*
* format_function_arguments: generate function name and argument list
*
* This is used when we can rely on pg_get_function_arguments to format
@@ -11417,15 +11434,22 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
int i_aggtransfn;
int i_aggfinalfn;
int i_aggsortop;
+ int i_aggtranssortop;
+ int i_hypothetical;
+ int i_isstrict;
int i_aggtranstype;
int i_agginitval;
int i_convertok;
const char *aggtransfn;
const char *aggfinalfn;
const char *aggsortop;
+ const char *aggtranssortop;
const char *aggtranstype;
const char *agginitval;
+ bool hypothetical;
+ bool isstrict;
bool convertok;
+ bool has_comma = false;
/* Skip if not to be dumped */
if (!agginfo->aggfn.dobj.dump || dataOnly)
@@ -11441,11 +11465,31 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
selectSourceSchema(fout, agginfo->aggfn.dobj.namespace->dobj.name);
/* Get aggregate-specific details */
- if (fout->remoteVersion >= 80400)
+ if (fout->remoteVersion >= 90400)
+ {
+ appendPQExpBuffer(query, "SELECT aggtransfn, "
+ "aggfinalfn, aggtranstype::pg_catalog.regtype, "
+ "aggsortop::pg_catalog.regoperator, "
+ "aggtranssortop::pg_catalog.regoperator, "
+ "(aggordnargs = -2) as hypothetical, "
+ "p.proisstrict as isstrict, "
+ "agginitval, "
+ "'t'::boolean AS convertok, "
+ "pg_catalog.pg_get_aggregate_arguments(p.oid) AS funcargs, "
+ "pg_catalog.pg_get_aggregate_identity_arguments(p.oid) AS funciargs "
+ "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
+ "WHERE a.aggfnoid = p.oid "
+ "AND p.oid = '%u'::pg_catalog.oid",
+ agginfo->aggfn.dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 80400)
{
appendPQExpBuffer(query, "SELECT aggtransfn, "
"aggfinalfn, aggtranstype::pg_catalog.regtype, "
"aggsortop::pg_catalog.regoperator, "
+ "0 as aggtranssortop, "
+ "false as hypothetical, "
+ "false as isstrict, "
"agginitval, "
"'t'::boolean AS convertok, "
"pg_catalog.pg_get_function_arguments(p.oid) AS funcargs, "
@@ -11460,6 +11504,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
appendPQExpBuffer(query, "SELECT aggtransfn, "
"aggfinalfn, aggtranstype::pg_catalog.regtype, "
"aggsortop::pg_catalog.regoperator, "
+ "0 as aggtranssortop, "
+ "false as hypothetical, "
+ "false as isstrict, "
"agginitval, "
"'t'::boolean AS convertok "
"FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
@@ -11472,6 +11519,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
appendPQExpBuffer(query, "SELECT aggtransfn, "
"aggfinalfn, aggtranstype::pg_catalog.regtype, "
"0 AS aggsortop, "
+ "0 as aggtranssortop, "
+ "'f'::boolean as hypothetical, "
+ "'f'::boolean as isstrict, "
"agginitval, "
"'t'::boolean AS convertok "
"FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
@@ -11484,6 +11534,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
appendPQExpBuffer(query, "SELECT aggtransfn, aggfinalfn, "
"format_type(aggtranstype, NULL) AS aggtranstype, "
"0 AS aggsortop, "
+ "0 as aggtranssortop, "
+ "'f'::boolean as hypothetical, "
+ "'f'::boolean as isstrict, "
"agginitval, "
"'t'::boolean AS convertok "
"FROM pg_aggregate "
@@ -11496,6 +11549,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
"aggfinalfn, "
"(SELECT typname FROM pg_type WHERE oid = aggtranstype1) AS aggtranstype, "
"0 AS aggsortop, "
+ "0 as aggtranssortop, "
+ "'f'::boolean as hypothetical, "
+ "'f'::boolean as isstrict, "
"agginitval1 AS agginitval, "
"(aggtransfn2 = 0 and aggtranstype2 = 0 and agginitval2 is null) AS convertok "
"FROM pg_aggregate "
@@ -11508,6 +11564,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
i_aggtransfn = PQfnumber(res, "aggtransfn");
i_aggfinalfn = PQfnumber(res, "aggfinalfn");
i_aggsortop = PQfnumber(res, "aggsortop");
+ i_aggtranssortop = PQfnumber(res, "aggtranssortop");
+ i_hypothetical = PQfnumber(res, "hypothetical");
+ i_isstrict = PQfnumber(res, "isstrict");
i_aggtranstype = PQfnumber(res, "aggtranstype");
i_agginitval = PQfnumber(res, "agginitval");
i_convertok = PQfnumber(res, "convertok");
@@ -11515,11 +11574,25 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
aggsortop = PQgetvalue(res, 0, i_aggsortop);
+ aggtranssortop = PQgetvalue(res, 0, i_aggtranssortop);
+ hypothetical = (PQgetvalue(res, 0, i_hypothetical)[0] == 't');
+ isstrict = (PQgetvalue(res, 0, i_isstrict)[0] == 't');
aggtranstype = PQgetvalue(res, 0, i_aggtranstype);
agginitval = PQgetvalue(res, 0, i_agginitval);
convertok = (PQgetvalue(res, 0, i_convertok)[0] == 't');
- if (fout->remoteVersion >= 80400)
+ if (fout->remoteVersion >= 90400)
+ {
+ /* 9.4 or later; we rely on server-side code for almost all of the work */
+ char *funcargs;
+ char *funciargs;
+
+ funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs"));
+ funciargs = PQgetvalue(res, 0, PQfnumber(res, "funciargs"));
+ aggfullsig = format_aggregate_arguments(&agginfo->aggfn, funcargs);
+ aggsig = format_aggregate_arguments(&agginfo->aggfn, funciargs);
+ }
+ else if (fout->remoteVersion >= 80400)
{
/* 8.4 or later; we rely on server-side code for most of the work */
char *funcargs;
@@ -11549,36 +11622,58 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
if (fout->remoteVersion >= 70300)
{
/* If using 7.3's regproc or regtype, data is already quoted */
- appendPQExpBuffer(details, " SFUNC = %s,\n STYPE = %s",
- aggtransfn,
- aggtranstype);
+ /*
+ * either or both of SFUNC and STYPE might be missing in >90400,
+ * but if SFUNC is missing, then FINALFUNC will always be present,
+ * and if SFUNC is present then STYPE must also be present; the
+ * code below relies on these conditions to keep the commas in the
+ * right places. STRICT must be forced to false if SFUNC is present.
+ */
+
+ if (strcmp(aggtransfn,"-") != 0)
+ {
+ appendPQExpBuffer(details, "\n SFUNC = %s,", aggtransfn);
+ isstrict = false;
+ }
+
+ if (strcmp(aggtranstype,"-") != 0)
+ appendPQExpBuffer(details, "\n STYPE = %s", aggtranstype);
+ else
+ has_comma = true;
}
else if (fout->remoteVersion >= 70100)
{
/* format_type quotes, regproc does not */
- appendPQExpBuffer(details, " SFUNC = %s,\n STYPE = %s",
+ appendPQExpBuffer(details, "\n SFUNC = %s,\n STYPE = %s",
fmtId(aggtransfn),
aggtranstype);
}
else
{
/* need quotes all around */
- appendPQExpBuffer(details, " SFUNC = %s,\n",
+ appendPQExpBuffer(details, "\n SFUNC = %s,\n",
fmtId(aggtransfn));
appendPQExpBuffer(details, " STYPE = %s",
fmtId(aggtranstype));
}
- if (!PQgetisnull(res, 0, i_agginitval))
+ if (strcmp(aggfinalfn, "-") != 0)
{
- appendPQExpBuffer(details, ",\n INITCOND = ");
- appendStringLiteralAH(details, agginitval, fout);
+ appendPQExpBuffer(details, "%s\n FINALFUNC = %s",
+ (has_comma ? "" : ","),
+ aggfinalfn);
}
- if (strcmp(aggfinalfn, "-") != 0)
+ if (hypothetical)
+ appendPQExpBuffer(details, ",\n HYPOTHETICAL");
+
+ if (isstrict)
+ appendPQExpBuffer(details, ",\n STRICT");
+
+ if (!PQgetisnull(res, 0, i_agginitval))
{
- appendPQExpBuffer(details, ",\n FINALFUNC = %s",
- aggfinalfn);
+ appendPQExpBuffer(details, ",\n INITCOND = ");
+ appendStringLiteralAH(details, agginitval, fout);
}
aggsortop = convertOperatorReference(fout, aggsortop);
@@ -11588,6 +11683,13 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
aggsortop);
}
+ aggtranssortop = convertOperatorReference(fout, aggtranssortop);
+ if (aggtranssortop)
+ {
+ appendPQExpBuffer(details, ",\n TRANSSORTOP = %s",
+ aggtranssortop);
+ }
+
/*
* DROP must be fully qualified in case same name appears in pg_catalog
*/
@@ -11595,7 +11697,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
fmtId(agginfo->aggfn.dobj.namespace->dobj.name),
aggsig);
- appendPQExpBuffer(q, "CREATE AGGREGATE %s (\n%s\n);\n",
+ appendPQExpBuffer(q, "CREATE AGGREGATE %s (%s\n);\n",
aggfullsig, details->data);
appendPQExpBuffer(labelq, "AGGREGATE %s", aggsig);
@@ -11624,7 +11726,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
/*
* Since there is no GRANT ON AGGREGATE syntax, we have to make the ACL
* command look like a function's GRANT; in particular this affects the
- * syntax for zero-argument aggregates.
+ * syntax for zero-argument aggregates and ordered set functions.
*/
free(aggsig);
free(aggsig_tag);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ed1c5fd..ee2a951 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -72,7 +72,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Name"),
gettext_noop("Result data type"));
- if (pset.sversion >= 80400)
+ if (pset.sversion >= 90400)
+ appendPQExpBuffer(&buf,
+ " pg_catalog.pg_get_aggregate_arguments(p.oid) AS \"%s\",\n",
+ gettext_noop("Argument data types"));
+ else if (pset.sversion >= 80400)
appendPQExpBuffer(&buf,
" CASE WHEN p.pronargs = 0\n"
" THEN CAST('*' AS pg_catalog.text)\n"
@@ -254,7 +258,26 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
gettext_noop("Schema"),
gettext_noop("Name"));
- if (pset.sversion >= 80400)
+ if (pset.sversion >= 90400)
+ appendPQExpBuffer(&buf,
+ " pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n"
+ " CASE WHEN p.proisagg THEN pg_catalog.pg_get_aggregate_arguments(p.oid)\n"
+ " ELSE pg_catalog.pg_get_function_arguments(p.oid) END as \"%s\",\n"
+ " CASE\n"
+ " WHEN p.proisagg THEN '%s'\n"
+ " WHEN p.proiswindow THEN '%s'\n"
+ " WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN '%s'\n"
+ " ELSE '%s'\n"
+ " END as \"%s\"",
+ gettext_noop("Result data type"),
+ gettext_noop("Argument data types"),
+ /* translator: "agg" is short for "aggregate" */
+ gettext_noop("agg"),
+ gettext_noop("window"),
+ gettext_noop("trigger"),
+ gettext_noop("normal"),
+ gettext_noop("Type"));
+ else if (pset.sversion >= 80400)
appendPQExpBuffer(&buf,
" pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n"
" pg_catalog.pg_get_function_arguments(p.oid) as \"%s\",\n"
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 5ad6ea6..b546ca4 100644
--- a/src/include/catalog/pg_aggregate.h
+++ b/src/include/catalog/pg_aggregate.h
@@ -32,6 +32,9 @@
* aggfinalfn final function (0 if none)
* aggsortop associated sort operator (0 if none)
* aggtranstype type of aggregate's transition (state) data
+ * aggtranssortop An optional sort operator for the type aggtranstype
+ * aggordnargs Number of direct arguments to aggregate.
+ * aggisordsetfunc A flag to represent whether a function is ordered set or not
* agginitval initial value for transition state (can be NULL)
* ----------------------------------------------------------------
*/
@@ -44,6 +47,9 @@ CATALOG(pg_aggregate,2600) BKI_WITHOUT_OIDS
regproc aggfinalfn;
Oid aggsortop;
Oid aggtranstype;
+ Oid aggtranssortop;
+ int32 aggordnargs;
+ bool aggisordsetfunc;
#ifdef CATALOG_VARLEN /* variable-length fields start here */
text agginitval;
@@ -62,13 +68,16 @@ typedef FormData_pg_aggregate *Form_pg_aggregate;
* ----------------
*/
-#define Natts_pg_aggregate 6
+#define Natts_pg_aggregate 9
#define Anum_pg_aggregate_aggfnoid 1
#define Anum_pg_aggregate_aggtransfn 2
#define Anum_pg_aggregate_aggfinalfn 3
#define Anum_pg_aggregate_aggsortop 4
#define Anum_pg_aggregate_aggtranstype 5
-#define Anum_pg_aggregate_agginitval 6
+#define Anum_pg_aggregate_aggtranssortop 6
+#define Anum_pg_aggregate_aggordnargs 7
+#define Anum_pg_aggregate_aggisordsetfunc 8
+#define Anum_pg_aggregate_agginitval 9
/* ----------------
@@ -77,163 +86,176 @@ typedef FormData_pg_aggregate *Form_pg_aggregate;
*/
/* avg */
-DATA(insert ( 2100 int8_avg_accum numeric_avg 0 1231 "{0,0}" ));
-DATA(insert ( 2101 int4_avg_accum int8_avg 0 1016 "{0,0}" ));
-DATA(insert ( 2102 int2_avg_accum int8_avg 0 1016 "{0,0}" ));
-DATA(insert ( 2103 numeric_avg_accum numeric_avg 0 1231 "{0,0}" ));
-DATA(insert ( 2104 float4_accum float8_avg 0 1022 "{0,0,0}" ));
-DATA(insert ( 2105 float8_accum float8_avg 0 1022 "{0,0,0}" ));
-DATA(insert ( 2106 interval_accum interval_avg 0 1187 "{0 second,0 second}" ));
+DATA(insert ( 2100 int8_avg_accum numeric_avg 0 1231 0 -1 f "{0,0}" ));
+DATA(insert ( 2101 int4_avg_accum int8_avg 0 1016 0 -1 f "{0,0}" ));
+DATA(insert ( 2102 int2_avg_accum int8_avg 0 1016 0 -1 f "{0,0}" ));
+DATA(insert ( 2103 numeric_avg_accum numeric_avg 0 1231 0 -1 f "{0,0}" ));
+DATA(insert ( 2104 float4_accum float8_avg 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2105 float8_accum float8_avg 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2106 interval_accum interval_avg 0 1187 0 -1 f "{0 second,0 second}" ));
/* sum */
-DATA(insert ( 2107 int8_sum - 0 1700 _null_ ));
-DATA(insert ( 2108 int4_sum - 0 20 _null_ ));
-DATA(insert ( 2109 int2_sum - 0 20 _null_ ));
-DATA(insert ( 2110 float4pl - 0 700 _null_ ));
-DATA(insert ( 2111 float8pl - 0 701 _null_ ));
-DATA(insert ( 2112 cash_pl - 0 790 _null_ ));
-DATA(insert ( 2113 interval_pl - 0 1186 _null_ ));
-DATA(insert ( 2114 numeric_add - 0 1700 _null_ ));
+DATA(insert ( 2107 int8_sum - 0 1700 0 -1 f _null_ ));
+DATA(insert ( 2108 int4_sum - 0 20 0 -1 f _null_ ));
+DATA(insert ( 2109 int2_sum - 0 20 0 -1 f _null_ ));
+DATA(insert ( 2110 float4pl - 0 700 0 -1 f _null_ ));
+DATA(insert ( 2111 float8pl - 0 701 0 -1 f _null_ ));
+DATA(insert ( 2112 cash_pl - 0 790 0 -1 f _null_ ));
+DATA(insert ( 2113 interval_pl - 0 1186 0 -1 f _null_ ));
+DATA(insert ( 2114 numeric_add - 0 1700 0 -1 f _null_ ));
/* max */
-DATA(insert ( 2115 int8larger - 413 20 _null_ ));
-DATA(insert ( 2116 int4larger - 521 23 _null_ ));
-DATA(insert ( 2117 int2larger - 520 21 _null_ ));
-DATA(insert ( 2118 oidlarger - 610 26 _null_ ));
-DATA(insert ( 2119 float4larger - 623 700 _null_ ));
-DATA(insert ( 2120 float8larger - 674 701 _null_ ));
-DATA(insert ( 2121 int4larger - 563 702 _null_ ));
-DATA(insert ( 2122 date_larger - 1097 1082 _null_ ));
-DATA(insert ( 2123 time_larger - 1112 1083 _null_ ));
-DATA(insert ( 2124 timetz_larger - 1554 1266 _null_ ));
-DATA(insert ( 2125 cashlarger - 903 790 _null_ ));
-DATA(insert ( 2126 timestamp_larger - 2064 1114 _null_ ));
-DATA(insert ( 2127 timestamptz_larger - 1324 1184 _null_ ));
-DATA(insert ( 2128 interval_larger - 1334 1186 _null_ ));
-DATA(insert ( 2129 text_larger - 666 25 _null_ ));
-DATA(insert ( 2130 numeric_larger - 1756 1700 _null_ ));
-DATA(insert ( 2050 array_larger - 1073 2277 _null_ ));
-DATA(insert ( 2244 bpchar_larger - 1060 1042 _null_ ));
-DATA(insert ( 2797 tidlarger - 2800 27 _null_ ));
-DATA(insert ( 3526 enum_larger - 3519 3500 _null_ ));
+DATA(insert ( 2115 int8larger - 413 20 0 -1 f _null_ ));
+DATA(insert ( 2116 int4larger - 521 23 0 -1 f _null_ ));
+DATA(insert ( 2117 int2larger - 520 21 0 -1 f _null_ ));
+DATA(insert ( 2118 oidlarger - 610 26 0 -1 f _null_ ));
+DATA(insert ( 2119 float4larger - 623 700 0 -1 f _null_ ));
+DATA(insert ( 2120 float8larger - 674 701 0 -1 f _null_ ));
+DATA(insert ( 2121 int4larger - 563 702 0 -1 f _null_ ));
+DATA(insert ( 2122 date_larger - 1097 1082 0 -1 f _null_ ));
+DATA(insert ( 2123 time_larger - 1112 1083 0 -1 f _null_ ));
+DATA(insert ( 2124 timetz_larger - 1554 1266 0 -1 f _null_ ));
+DATA(insert ( 2125 cashlarger - 903 790 0 -1 f _null_ ));
+DATA(insert ( 2126 timestamp_larger - 2064 1114 0 -1 f _null_ ));
+DATA(insert ( 2127 timestamptz_larger - 1324 1184 0 -1 f _null_ ));
+DATA(insert ( 2128 interval_larger - 1334 1186 0 -1 f _null_ ));
+DATA(insert ( 2129 text_larger - 666 25 0 -1 f _null_ ));
+DATA(insert ( 2130 numeric_larger - 1756 1700 0 -1 f _null_ ));
+DATA(insert ( 2050 array_larger - 1073 2277 0 -1 f _null_ ));
+DATA(insert ( 2244 bpchar_larger - 1060 1042 0 -1 f _null_ ));
+DATA(insert ( 2797 tidlarger - 2800 27 0 -1 f _null_ ));
+DATA(insert ( 3526 enum_larger - 3519 3500 0 -1 f _null_ ));
/* min */
-DATA(insert ( 2131 int8smaller - 412 20 _null_ ));
-DATA(insert ( 2132 int4smaller - 97 23 _null_ ));
-DATA(insert ( 2133 int2smaller - 95 21 _null_ ));
-DATA(insert ( 2134 oidsmaller - 609 26 _null_ ));
-DATA(insert ( 2135 float4smaller - 622 700 _null_ ));
-DATA(insert ( 2136 float8smaller - 672 701 _null_ ));
-DATA(insert ( 2137 int4smaller - 562 702 _null_ ));
-DATA(insert ( 2138 date_smaller - 1095 1082 _null_ ));
-DATA(insert ( 2139 time_smaller - 1110 1083 _null_ ));
-DATA(insert ( 2140 timetz_smaller - 1552 1266 _null_ ));
-DATA(insert ( 2141 cashsmaller - 902 790 _null_ ));
-DATA(insert ( 2142 timestamp_smaller - 2062 1114 _null_ ));
-DATA(insert ( 2143 timestamptz_smaller - 1322 1184 _null_ ));
-DATA(insert ( 2144 interval_smaller - 1332 1186 _null_ ));
-DATA(insert ( 2145 text_smaller - 664 25 _null_ ));
-DATA(insert ( 2146 numeric_smaller - 1754 1700 _null_ ));
-DATA(insert ( 2051 array_smaller - 1072 2277 _null_ ));
-DATA(insert ( 2245 bpchar_smaller - 1058 1042 _null_ ));
-DATA(insert ( 2798 tidsmaller - 2799 27 _null_ ));
-DATA(insert ( 3527 enum_smaller - 3518 3500 _null_ ));
+DATA(insert ( 2131 int8smaller - 412 20 0 -1 f _null_ ));
+DATA(insert ( 2132 int4smaller - 97 23 0 -1 f _null_ ));
+DATA(insert ( 2133 int2smaller - 95 21 0 -1 f _null_ ));
+DATA(insert ( 2134 oidsmaller - 609 26 0 -1 f _null_ ));
+DATA(insert ( 2135 float4smaller - 622 700 0 -1 f _null_ ));
+DATA(insert ( 2136 float8smaller - 672 701 0 -1 f _null_ ));
+DATA(insert ( 2137 int4smaller - 562 702 0 -1 f _null_ ));
+DATA(insert ( 2138 date_smaller - 1095 1082 0 -1 f _null_ ));
+DATA(insert ( 2139 time_smaller - 1110 1083 0 -1 f _null_ ));
+DATA(insert ( 2140 timetz_smaller - 1552 1266 0 -1 f _null_ ));
+DATA(insert ( 2141 cashsmaller - 902 790 0 -1 f _null_ ));
+DATA(insert ( 2142 timestamp_smaller - 2062 1114 0 -1 f _null_ ));
+DATA(insert ( 2143 timestamptz_smaller - 1322 1184 0 -1 f _null_ ));
+DATA(insert ( 2144 interval_smaller - 1332 1186 0 -1 f _null_ ));
+DATA(insert ( 2145 text_smaller - 664 25 0 -1 f _null_ ));
+DATA(insert ( 2146 numeric_smaller - 1754 1700 0 -1 f _null_ ));
+DATA(insert ( 2051 array_smaller - 1072 2277 0 -1 f _null_ ));
+DATA(insert ( 2245 bpchar_smaller - 1058 1042 0 -1 f _null_ ));
+DATA(insert ( 2798 tidsmaller - 2799 27 0 -1 f _null_ ));
+DATA(insert ( 3527 enum_smaller - 3518 3500 0 -1 f _null_ ));
/* count */
-DATA(insert ( 2147 int8inc_any - 0 20 "0" ));
-DATA(insert ( 2803 int8inc - 0 20 "0" ));
+DATA(insert ( 2147 int8inc_any - 0 20 0 -1 f "0" ));
+DATA(insert ( 2803 int8inc - 0 20 0 -1 f "0" ));
/* var_pop */
-DATA(insert ( 2718 int8_accum numeric_var_pop 0 1231 "{0,0,0}" ));
-DATA(insert ( 2719 int4_accum numeric_var_pop 0 1231 "{0,0,0}" ));
-DATA(insert ( 2720 int2_accum numeric_var_pop 0 1231 "{0,0,0}" ));
-DATA(insert ( 2721 float4_accum float8_var_pop 0 1022 "{0,0,0}" ));
-DATA(insert ( 2722 float8_accum float8_var_pop 0 1022 "{0,0,0}" ));
-DATA(insert ( 2723 numeric_accum numeric_var_pop 0 1231 "{0,0,0}" ));
+DATA(insert ( 2718 int8_accum numeric_var_pop 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2719 int4_accum numeric_var_pop 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2720 int2_accum numeric_var_pop 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2721 float4_accum float8_var_pop 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2722 float8_accum float8_var_pop 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2723 numeric_accum numeric_var_pop 0 1231 0 -1 f "{0,0,0}" ));
/* var_samp */
-DATA(insert ( 2641 int8_accum numeric_var_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2642 int4_accum numeric_var_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2643 int2_accum numeric_var_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2644 float4_accum float8_var_samp 0 1022 "{0,0,0}" ));
-DATA(insert ( 2645 float8_accum float8_var_samp 0 1022 "{0,0,0}" ));
-DATA(insert ( 2646 numeric_accum numeric_var_samp 0 1231 "{0,0,0}" ));
+DATA(insert ( 2641 int8_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2642 int4_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2643 int2_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2644 float4_accum float8_var_samp 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2645 float8_accum float8_var_samp 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2646 numeric_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" ));
/* variance: historical Postgres syntax for var_samp */
-DATA(insert ( 2148 int8_accum numeric_var_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2149 int4_accum numeric_var_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2150 int2_accum numeric_var_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2151 float4_accum float8_var_samp 0 1022 "{0,0,0}" ));
-DATA(insert ( 2152 float8_accum float8_var_samp 0 1022 "{0,0,0}" ));
-DATA(insert ( 2153 numeric_accum numeric_var_samp 0 1231 "{0,0,0}" ));
+DATA(insert ( 2148 int8_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2149 int4_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2150 int2_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2151 float4_accum float8_var_samp 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2152 float8_accum float8_var_samp 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2153 numeric_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" ));
/* stddev_pop */
-DATA(insert ( 2724 int8_accum numeric_stddev_pop 0 1231 "{0,0,0}" ));
-DATA(insert ( 2725 int4_accum numeric_stddev_pop 0 1231 "{0,0,0}" ));
-DATA(insert ( 2726 int2_accum numeric_stddev_pop 0 1231 "{0,0,0}" ));
-DATA(insert ( 2727 float4_accum float8_stddev_pop 0 1022 "{0,0,0}" ));
-DATA(insert ( 2728 float8_accum float8_stddev_pop 0 1022 "{0,0,0}" ));
-DATA(insert ( 2729 numeric_accum numeric_stddev_pop 0 1231 "{0,0,0}" ));
+DATA(insert ( 2724 int8_accum numeric_stddev_pop 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2725 int4_accum numeric_stddev_pop 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2726 int2_accum numeric_stddev_pop 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2727 float4_accum float8_stddev_pop 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2728 float8_accum float8_stddev_pop 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2729 numeric_accum numeric_stddev_pop 0 1231 0 -1 f "{0,0,0}" ));
/* stddev_samp */
-DATA(insert ( 2712 int8_accum numeric_stddev_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2713 int4_accum numeric_stddev_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2714 int2_accum numeric_stddev_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2715 float4_accum float8_stddev_samp 0 1022 "{0,0,0}" ));
-DATA(insert ( 2716 float8_accum float8_stddev_samp 0 1022 "{0,0,0}" ));
-DATA(insert ( 2717 numeric_accum numeric_stddev_samp 0 1231 "{0,0,0}" ));
+DATA(insert ( 2712 int8_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2713 int4_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2714 int2_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2715 float4_accum float8_stddev_samp 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2716 float8_accum float8_stddev_samp 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2717 numeric_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" ));
/* stddev: historical Postgres syntax for stddev_samp */
-DATA(insert ( 2154 int8_accum numeric_stddev_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2155 int4_accum numeric_stddev_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2156 int2_accum numeric_stddev_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2157 float4_accum float8_stddev_samp 0 1022 "{0,0,0}" ));
-DATA(insert ( 2158 float8_accum float8_stddev_samp 0 1022 "{0,0,0}" ));
-DATA(insert ( 2159 numeric_accum numeric_stddev_samp 0 1231 "{0,0,0}" ));
+DATA(insert ( 2154 int8_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2155 int4_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2156 int2_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2157 float4_accum float8_stddev_samp 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2158 float8_accum float8_stddev_samp 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2159 numeric_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" ));
/* SQL2003 binary regression aggregates */
-DATA(insert ( 2818 int8inc_float8_float8 - 0 20 "0" ));
-DATA(insert ( 2819 float8_regr_accum float8_regr_sxx 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2820 float8_regr_accum float8_regr_syy 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2821 float8_regr_accum float8_regr_sxy 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2822 float8_regr_accum float8_regr_avgx 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2823 float8_regr_accum float8_regr_avgy 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2824 float8_regr_accum float8_regr_r2 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2825 float8_regr_accum float8_regr_slope 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2826 float8_regr_accum float8_regr_intercept 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2827 float8_regr_accum float8_covar_pop 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2828 float8_regr_accum float8_covar_samp 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2829 float8_regr_accum float8_corr 0 1022 "{0,0,0,0,0,0}" ));
+DATA(insert ( 2818 int8inc_float8_float8 - 0 20 0 -1 f "0" ));
+DATA(insert ( 2819 float8_regr_accum float8_regr_sxx 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2820 float8_regr_accum float8_regr_syy 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2821 float8_regr_accum float8_regr_sxy 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2822 float8_regr_accum float8_regr_avgx 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2823 float8_regr_accum float8_regr_avgy 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2824 float8_regr_accum float8_regr_r2 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2825 float8_regr_accum float8_regr_slope 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2826 float8_regr_accum float8_regr_intercept 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2827 float8_regr_accum float8_covar_pop 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2828 float8_regr_accum float8_covar_samp 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2829 float8_regr_accum float8_corr 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
/* boolean-and and boolean-or */
-DATA(insert ( 2517 booland_statefunc - 58 16 _null_ ));
-DATA(insert ( 2518 boolor_statefunc - 59 16 _null_ ));
-DATA(insert ( 2519 booland_statefunc - 58 16 _null_ ));
+DATA(insert ( 2517 booland_statefunc - 58 16 0 -1 f _null_ ));
+DATA(insert ( 2518 boolor_statefunc - 59 16 0 -1 f _null_ ));
+DATA(insert ( 2519 booland_statefunc - 58 16 0 -1 f _null_ ));
/* bitwise integer */
-DATA(insert ( 2236 int2and - 0 21 _null_ ));
-DATA(insert ( 2237 int2or - 0 21 _null_ ));
-DATA(insert ( 2238 int4and - 0 23 _null_ ));
-DATA(insert ( 2239 int4or - 0 23 _null_ ));
-DATA(insert ( 2240 int8and - 0 20 _null_ ));
-DATA(insert ( 2241 int8or - 0 20 _null_ ));
-DATA(insert ( 2242 bitand - 0 1560 _null_ ));
-DATA(insert ( 2243 bitor - 0 1560 _null_ ));
+DATA(insert ( 2236 int2and - 0 21 0 -1 f _null_ ));
+DATA(insert ( 2237 int2or - 0 21 0 -1 f _null_ ));
+DATA(insert ( 2238 int4and - 0 23 0 -1 f _null_ ));
+DATA(insert ( 2239 int4or - 0 23 0 -1 f _null_ ));
+DATA(insert ( 2240 int8and - 0 20 0 -1 f _null_ ));
+DATA(insert ( 2241 int8or - 0 20 0 -1 f _null_ ));
+DATA(insert ( 2242 bitand - 0 1560 0 -1 f _null_ ));
+DATA(insert ( 2243 bitor - 0 1560 0 -1 f _null_ ));
/* xml */
-DATA(insert ( 2901 xmlconcat2 - 0 142 _null_ ));
+DATA(insert ( 2901 xmlconcat2 - 0 142 0 -1 f _null_ ));
/* array */
-DATA(insert ( 2335 array_agg_transfn array_agg_finalfn 0 2281 _null_ ));
+DATA(insert ( 2335 array_agg_transfn array_agg_finalfn 0 2281 0 -1 f _null_ ));
/* text */
-DATA(insert ( 3538 string_agg_transfn string_agg_finalfn 0 2281 _null_ ));
+DATA(insert ( 3538 string_agg_transfn string_agg_finalfn 0 2281 0 -1 f _null_ ));
/* bytea */
-DATA(insert ( 3545 bytea_string_agg_transfn bytea_string_agg_finalfn 0 2281 _null_ ));
+DATA(insert ( 3545 bytea_string_agg_transfn bytea_string_agg_finalfn 0 2281 0 -1 f _null_ ));
/* json */
-DATA(insert ( 3175 json_agg_transfn json_agg_finalfn 0 2281 _null_ ));
+DATA(insert ( 3175 json_agg_transfn json_agg_finalfn 0 2281 0 -1 f _null_ ));
+
+/* ordered set functions */
+DATA(insert ( 3931 - percentile_disc_final 0 0 0 1 t _null_));
+DATA(insert ( 3935 - percentile_cont_float8_final 0 0 0 1 t _null_));
+DATA(insert ( 3939 - percentile_cont_interval_final 0 0 0 1 t _null_));
+DATA(insert ( 3920 - rank_final 0 16 59 -2 t "f"));
+DATA(insert ( 3970 - dense_rank_final 0 16 59 -2 t "f"));
+DATA(insert ( 3972 - percent_rank_final 0 16 59 -2 t "f"));
+DATA(insert ( 3974 - cume_dist_final 0 16 58 -2 t "f"));
+DATA(insert ( 3976 - mode_final 0 0 0 0 t _null_));
+DATA(insert ( 3978 - percentile_disc_multi_final 0 0 0 1 t _null_));
+DATA(insert ( 3980 - percentile_cont_float8_multi_final 0 0 0 1 t _null_));
+DATA(insert ( 3982 - percentile_cont_interval_multi_final 0 0 0 1 t _null_));
/*
* prototypes for functions in pg_aggregate.c
@@ -241,6 +263,7 @@ DATA(insert ( 3175 json_agg_transfn json_agg_finalfn 0 2281 _null_ ));
extern Oid AggregateCreate(const char *aggName,
Oid aggNamespace,
int numArgs,
+ int numDirectArgs,
oidvector *parameterTypes,
Datum allParameterTypes,
Datum parameterModes,
@@ -249,7 +272,11 @@ extern Oid AggregateCreate(const char *aggName,
List *aggtransfnName,
List *aggfinalfnName,
List *aggsortopName,
+ List *aggtranssortopName,
Oid aggTransType,
- const char *agginitval);
+ const char *agginitval,
+ bool isStrict,
+ bool isOrderedSetFunc,
+ bool isHypotheticalSet);
#endif /* PG_AGGREGATE_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ca4fc62..a0d2e12 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1973,6 +1973,10 @@ DATA(insert OID = 2232 ( pg_get_function_identity_arguments PGNSP PGUID 12 1
DESCR("identity argument list of a function");
DATA(insert OID = 2165 ( pg_get_function_result PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ pg_get_function_result _null_ _null_ _null_ ));
DESCR("result type of a function");
+DATA(insert OID = 3178 ( pg_get_aggregate_arguments PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ pg_get_aggregate_arguments _null_ _null_ _null_ ));
+DESCR("argument list of an aggregate function");
+DATA(insert OID = 3179 ( pg_get_aggregate_identity_arguments PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ pg_get_aggregate_identity_arguments _null_ _null_ _null_ ));
+DESCR("identity argument list of an aggregate function");
DATA(insert OID = 1686 ( pg_get_keywords PGNSP PGUID 12 10 400 0 0 f f f f t t s 0 0 2249 "" "{25,18,25}" "{o,o,o}" "{word,catcode,catdesc}" _null_ pg_get_keywords _null_ _null_ _null_ ));
DESCR("list of SQL keywords");
@@ -4750,6 +4754,74 @@ DESCR("SP-GiST support for quad tree over range");
/* event triggers */
DATA(insert OID = 3566 ( pg_event_trigger_dropped_objects PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25}" "{o,o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, schema_name, object_name, object_identity}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
DESCR("list objects dropped by the current command");
+
+/* inverse distribution functions */
+DATA(insert OID = 3931 ( percentile_disc PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 2283 "701 2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("discrete percentile");
+
+DATA(insert OID = 3932 ( percentile_disc_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2283 "701 2283" _null_ _null_ _null_ _null_ percentile_disc_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+DATA(insert OID = 3935 ( percentile_cont PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("continous distribution percentile for float8");
+
+DATA(insert OID = 3936 ( percentile_cont_float8_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 701 "701 701" _null_ _null_ _null_ _null_ percentile_cont_float8_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+DATA(insert OID = 3939 ( percentile_cont PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 1186 "701 1186" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("continous distribution percentile for interval");
+
+DATA(insert OID = 3940 ( percentile_cont_interval_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1186 "701 1186" _null_ _null_ _null_ _null_ percentile_cont_interval_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+/* hypothetical set functions */
+DATA(insert OID = 3920 ( rank PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("hypothetical rank");
+
+DATA(insert OID = 3969 ( rank_final PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_ hypothetical_rank_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+DATA(insert OID = 3970 ( dense_rank PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("rank of hypothetical row without gaps");
+
+DATA(insert OID = 3971 ( dense_rank_final PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_ hypothetical_dense_rank_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+DATA(insert OID = 3972 ( percent_rank PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("fractional ranking of hypothetical row within a group");
+
+DATA(insert OID = 3973 ( percent_rank_final PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_ hypothetical_percent_rank_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+DATA(insert OID = 3974 ( cume_dist PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("cumulative distribution of hypothetical row in a group");
+
+DATA(insert OID = 3975 ( cume_dist_final PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_ hypothetical_cume_dist_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+DATA(insert OID = 3976 ( mode PGNSP PGUID 12 1 0 0 0 t f f f t f i 1 0 2283 2283 _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("most common value in group");
+DATA(insert OID = 3977 ( mode_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2283 2283 _null_ _null_ _null_ _null_ mode_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+DATA(insert OID = 3978 ( percentile_disc PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 2277 "1022 2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("multiple discrete percentiles");
+
+DATA(insert OID = 3979 ( percentile_disc_multi_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2277 "1022 2283" _null_ _null_ _null_ _null_ percentile_disc_multi_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+DATA(insert OID = 3980 ( percentile_cont PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 1022 "1022 701" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("multiple continuous percentiles of float8 values");
+
+DATA(insert OID = 3981 ( percentile_cont_float8_multi_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1022 "1022 701" _null_ _null_ _null_ _null_ percentile_cont_float8_multi_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+DATA(insert OID = 3982 ( percentile_cont PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 1187 "1022 1186" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("multiple continuous percentiles of interval values");
+
+DATA(insert OID = 3983 ( percentile_cont_interval_multi_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1187 "1022 1186" _null_ _null_ _null_ _null_ percentile_cont_interval_multi_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
/*
* Symbolic values for provolatile column: these indicate whether the result
* of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 1f72e1b..50ee3de 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -651,6 +651,35 @@ extern void **find_rendezvous_variable(const char *varName);
extern int AggCheckCallContext(FunctionCallInfo fcinfo,
MemoryContext *aggcontext);
+typedef struct Tuplesortstate fmTuplesortstate;
+typedef struct tupleDesc *fmTupleDesc;
+typedef struct TupleTableSlot fmTupleTableSlot;
+
+extern int64 AggSetGetRowCount(FunctionCallInfo fcinfo);
+
+extern void AggSetGetSortInfo(FunctionCallInfo fcinfo,
+ fmTuplesortstate **sortstate,
+ fmTupleDesc *tupdesc,
+ fmTupleTableSlot **tupslot,
+ Oid *datumtype);
+
+/* int16 rather than AttrNumber here to avoid includes */
+extern int AggSetGetDistinctInfo(FunctionCallInfo fcinfo,
+ fmTupleTableSlot **tupslot,
+ int16 **sortColIdx,
+ FmgrInfo **equalfns);
+
+/* int16 rather than AttrNumber here to avoid includes */
+extern int AggSetGetSortOperators(FunctionCallInfo fcinfo,
+ int16 **sortColIdx,
+ Oid **sortOperators,
+ Oid **sortEqOperators,
+ Oid **sortCollations,
+ bool **sortNullsFirst);
+
+extern void AggSetGetPerTupleContext(FunctionCallInfo fcinfo,
+ MemoryContext *memcontext);
+
/*
* We allow plugin modules to hook function entry/exit. This is intended
* as support for loadable security policy modules, which may want to
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 3b430e0..ba9f328 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -588,6 +588,7 @@ typedef struct AggrefExprState
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ List *orddirectargs; /* Ordered direct arguments */
ExprState *aggfilter; /* FILTER expression */
int aggno; /* ID number for agg within its plan node */
} AggrefExprState;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 78368c6..c6035e0 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -425,7 +425,8 @@ typedef enum NodeTag
T_WindowObjectData, /* private in nodeWindowAgg.c */
T_TIDBitmap, /* in nodes/tidbitmap.h */
T_InlineCodeBlock, /* in nodes/parsenodes.h */
- T_FdwRoutine /* in foreign/fdwapi.h */
+ T_FdwRoutine, /* in foreign/fdwapi.h */
+ T_AggStatePerAggData /* private in nodeAgg.c */
} NodeTag;
/*
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e5235cb..414bb98 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -303,6 +303,7 @@ typedef struct FuncCall
bool agg_star; /* argument was really '*' */
bool agg_distinct; /* arguments were labeled DISTINCT */
bool func_variadic; /* last argument was labeled VARIADIC */
+ bool has_within_group; /* WITHIN GROUP clause,if any */
struct WindowDef *over; /* OVER clause, if any */
int location; /* token location, or -1 if unknown */
} FuncCall;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 7918537..594dddf 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -247,9 +247,12 @@ typedef struct Aggref
List *args; /* arguments and sort expressions */
List *aggorder; /* ORDER BY (list of SortGroupClause) */
List *aggdistinct; /* DISTINCT (list of SortGroupClause) */
+ List *orddirectargs; /* Direct arguments for ordered set functions */
Expr *aggfilter; /* FILTER expression */
bool aggstar; /* TRUE if argument list was really '*' */
bool aggvariadic; /* TRUE if VARIADIC was used in call */
+ bool isordset; /* If node is from an ordered set function */
+ bool ishypothetical; /* If node is from a hypothetical set function */
Index agglevelsup; /* > 0 if agg belongs to outer query */
int location; /* token location, or -1 if unknown */
} Aggref;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 8bd34d6..ab27156 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -412,6 +412,7 @@ PG_KEYWORD("where", WHERE, RESERVED_KEYWORD)
PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("window", WINDOW, RESERVED_KEYWORD)
PG_KEYWORD("with", WITH, RESERVED_KEYWORD)
+PG_KEYWORD("within", WITHIN, UNRESERVED_KEYWORD)
PG_KEYWORD("without", WITHOUT, UNRESERVED_KEYWORD)
PG_KEYWORD("work", WORK, UNRESERVED_KEYWORD)
PG_KEYWORD("wrapper", WRAPPER, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h
index b6d9dd3..5fe6295 100644
--- a/src/include/parser/parse_agg.h
+++ b/src/include/parser/parse_agg.h
@@ -17,7 +17,7 @@
extern void transformAggregateCall(ParseState *pstate, Aggref *agg,
List *args, List *aggorder,
- bool agg_distinct);
+ bool agg_distinct, bool agg_within_group);
extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
WindowDef *windef);
@@ -34,4 +34,18 @@ extern void build_aggregate_fnexprs(Oid *agg_input_types,
Expr **transfnexpr,
Expr **finalfnexpr);
+void
+build_orderedset_fnexprs(Oid *agg_input_types,
+ int agg_num_inputs,
+ bool agg_variadic,
+ Oid agg_result_type,
+ Oid agg_input_collation,
+ Oid *agg_input_collation_array,
+ Oid finalfn_oid,
+ Expr **finalfnexpr);
+
+int get_aggregate_argtypes(Aggref *aggref,
+ Oid *inputTypes,
+ Oid *inputCollations);
+
#endif /* PARSE_AGG_H */
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index 9bdb033..dcc08d0 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -31,7 +31,7 @@ extern List *transformGroupClause(ParseState *pstate, List *grouplist,
ParseExprKind exprKind, bool useSQL99);
extern List *transformSortClause(ParseState *pstate, List *orderlist,
List **targetlist, ParseExprKind exprKind,
- bool resolveUnknown, bool useSQL99);
+ bool resolveUnknown, bool useSQL99, bool keepDuplicates);
extern List *transformWindowDefinitions(ParseState *pstate,
List *windowdefs,
diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h
index d33eef3..834fc92 100644
--- a/src/include/parser/parse_func.h
+++ b/src/include/parser/parse_func.h
@@ -38,14 +38,11 @@ typedef enum
FUNCDETAIL_NORMAL, /* found a matching regular function */
FUNCDETAIL_AGGREGATE, /* found a matching aggregate function */
FUNCDETAIL_WINDOWFUNC, /* found a matching window function */
- FUNCDETAIL_COERCION /* it's a type coercion request */
+ FUNCDETAIL_COERCION, /* it's a type coercion request */
} FuncDetailCode;
-
extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
- List *agg_order, Expr *agg_filter,
- bool agg_star, bool agg_distinct, bool func_variadic,
- WindowDef *over, bool is_column, int location);
+ int location, FuncCall *fn);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
@@ -66,8 +63,10 @@ extern FuncCandidateList func_select_candidate(int nargs,
extern void make_fn_arguments(ParseState *pstate,
List *fargs,
+ List *agg_order,
Oid *actual_arg_types,
- Oid *declared_arg_types);
+ Oid *declared_arg_types,
+ bool requiresUnification);
extern const char *funcname_signature_string(const char *funcname, int nargs,
List *argnames, const Oid *argtypes);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index ce3f00b..8cda3d9 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -665,6 +665,8 @@ extern Datum pg_get_functiondef(PG_FUNCTION_ARGS);
extern Datum pg_get_function_arguments(PG_FUNCTION_ARGS);
extern Datum pg_get_function_identity_arguments(PG_FUNCTION_ARGS);
extern Datum pg_get_function_result(PG_FUNCTION_ARGS);
+extern Datum pg_get_aggregate_arguments(PG_FUNCTION_ARGS);
+extern Datum pg_get_aggregate_identity_arguments(PG_FUNCTION_ARGS);
extern char *deparse_expression(Node *expr, List *dpcontext,
bool forceprefix, bool showimplicit);
extern List *deparse_context_for(const char *aliasname, Oid relid);
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 1af79e5..990de50 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1249,6 +1249,213 @@ select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
{"(2,2,bar)","(3,1,baz)"}
(1 row)
+-- ordered set functions
+select p, percentile_cont(p) within group (order by x::float8) from generate_series(1,5) x,
+ (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p)
+ group by p order by p;
+ p | percentile_cont
+------+-----------------
+ 0 | 1
+ 0.1 | 1.4
+ 0.25 | 2
+ 0.4 | 2.6
+ 0.5 | 3
+ 0.6 | 3.4
+ 0.75 | 4
+ 0.9 | 4.6
+ 1 | 5
+(9 rows)
+
+select p, percentile_cont(p order by p) within group (order by x::float8)
+ from generate_series(1,5) x, (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p)
+ group by p order by x;
+ERROR: cannot have multiple ORDER BY clauses with WITHIN GROUP
+LINE 1: select p, percentile_cont(p order by p) within group (order ...
+ ^
+select p, sum() within group (order by x::float8)
+ from generate_series(1,5) x,
+ (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p;
+ERROR: sum(double precision) is not an ordered set function
+select p, percentile_cont(p,p) from generate_series(1,5) x,
+ (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p;
+ERROR: WITHIN GROUP is required for call to ordered set function percentile_cont
+LINE 1: select p, percentile_cont(p,p) from generate_series(1,5) x,
+ ^
+select percentile_cont(0.5) within group (order by b) from aggtest;
+ percentile_cont
+------------------
+ 53.4485001564026
+(1 row)
+
+select percentile_cont(0.5) within group (order by b),sum(b) from aggtest;
+ percentile_cont | sum
+------------------+---------
+ 53.4485001564026 | 431.773
+(1 row)
+
+select percentile_cont(0.5) within group (order by thousand) from tenk1;
+ percentile_cont
+-----------------
+ 499.5
+(1 row)
+
+select percentile_disc(0.5) within group (order by thousand) from tenk1;
+ percentile_disc
+-----------------
+ 499
+(1 row)
+
+select rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+ rank
+------
+ 5
+(1 row)
+
+select cume_dist(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+ cume_dist
+-----------
+ 0.875
+(1 row)
+
+select percent_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4),(5)) v(x);
+ percent_rank
+--------------
+ 0.5
+(1 row)
+
+select dense_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+ dense_rank
+------------
+ 3
+(1 row)
+
+select percentile_disc(array[0,0.1,0.25,0.5,0.75,0.9,1]) within group (order by thousand) from tenk1;
+ percentile_disc
+----------------------------
+ {0,99,249,499,749,899,999}
+(1 row)
+
+select percentile_cont(array[0,0.25,0.5,0.75,1]) within group (order by thousand) from tenk1;
+ percentile_cont
+-----------------------------
+ {0,249.75,499.5,749.25,999}
+(1 row)
+
+select percentile_disc(array[[null,1,0.5],[0.75,0.25,null]]) within group (order by thousand) from tenk1;
+ percentile_disc
+---------------------------------
+ {{NULL,999,499},{749,249,NULL}}
+(1 row)
+
+select percentile_cont(array[0,1,0.25,0.75,0.5,1]) within group (order by x)
+ from generate_series(1,6) x;
+ percentile_cont
+-----------------------
+ {1,6,2.25,4.75,3.5,5}
+(1 row)
+
+select ten, mode() within group (order by string4) from tenk1 group by ten;
+ ten | mode
+-----+--------
+ 0 | HHHHxx
+ 1 | OOOOxx
+ 2 | VVVVxx
+ 3 | OOOOxx
+ 4 | HHHHxx
+ 5 | HHHHxx
+ 6 | OOOOxx
+ 7 | AAAAxx
+ 8 | VVVVxx
+ 9 | VVVVxx
+(10 rows)
+
+select percentile_disc(array[0.25,0.5,0.75]) within group (order by x)
+ from unnest('{fred,jim,fred,jack,jill,fred,jill,jim,jim,sheila,jim,sheila}'::text[]) u(x);
+ percentile_disc
+-----------------
+ {fred,jill,jim}
+(1 row)
+
+-- ordered set funcs can't use ungrouped direct args:
+select rank(x) within group (order by x) from generate_series(1,5) x;
+ERROR: column "x.x" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: select rank(x) within group (order by x) from generate_serie...
+ ^
+-- collation:
+select pg_collation_for(percentile_disc(1) within group (order by x collate "POSIX"))
+ from (values ('fred'),('jim')) v(x);
+ pg_collation_for
+------------------
+ "POSIX"
+(1 row)
+
+-- hypothetical type unification and argument failures:
+select rank(3) within group (order by x) from (values ('fred'),('jim')) v(x);
+ERROR: WITHIN GROUP types text and integer cannot be matched
+LINE 1: select rank(3) within group (order by x) from (values ('fred...
+ ^
+select rank(3) within group (order by stringu1,stringu2) from tenk1;
+ERROR: function rank has 2 ordering columns but only 1 hypothetical arguments
+LINE 1: select rank(3) within group (order by stringu1,stringu2) fro...
+ ^
+select rank('fred') within group (order by x) from generate_series(1,5) x;
+ERROR: invalid input syntax for integer: "fred"
+LINE 1: select rank('fred') within group (order by x) from generate_...
+ ^
+select rank('adam'::text collate "C") within group (order by x collate "POSIX")
+ from (values ('fred'),('jim')) v(x);
+ERROR: collation mismatch between explicit collations "C" and "POSIX"
+LINE 1: ...adam'::text collate "C") within group (order by x collate "P...
+ ^
+-- hypothetical type unification successes:
+select rank('adam'::varchar) within group (order by x) from (values ('fred'),('jim')) v(x);
+ rank
+------
+ 1
+(1 row)
+
+select rank('3') within group (order by x) from generate_series(1,5) x;
+ rank
+------
+ 3
+(1 row)
+
+-- deparse and multiple features:
+create view aggordview1 as
+select ten,
+ percentile_disc(0.5) within group (order by thousand) as p50,
+ percentile_disc(0.5) within group (order by thousand) filter (where hundred=1) as px,
+ rank(5,'AZZZZ',50) within group (order by hundred, string4 desc, hundred)
+ from tenk1
+ group by ten order by ten;
+select pg_get_viewdef('aggordview1');
+ pg_get_viewdef
+--------------------------------------------------------------------------------------------------------------------------------
+ SELECT tenk1.ten, +
+ percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50, +
+ percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px, +
+ rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank +
+ FROM tenk1 +
+ GROUP BY tenk1.ten +
+ ORDER BY tenk1.ten;
+(1 row)
+
+select * from aggordview1 order by ten;
+ ten | p50 | px | rank
+-----+-----+-----+------
+ 0 | 490 | | 101
+ 1 | 491 | 401 | 101
+ 2 | 492 | | 101
+ 3 | 493 | | 101
+ 4 | 494 | | 101
+ 5 | 495 | | 67
+ 6 | 496 | | 1
+ 7 | 497 | | 1
+ 8 | 498 | | 1
+ 9 | 499 | | 1
+(10 rows)
+
+drop view aggordview1;
-- variadic aggregates
select least_agg(q1,q2) from int8_tbl;
least_agg
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 57d614f..c72bd78 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -700,9 +700,17 @@ SELECT * FROM funcdescs
-- **************** pg_aggregate ****************
-- Look for illegal values in pg_aggregate fields.
+-- ordered set functions can't have transfns, and must
+-- have finalfns, but may or may not have transtypes.
+-- other aggs must have transfns and transtypes with
+-- optional finalfns.
SELECT ctid, aggfnoid::oid
FROM pg_aggregate as p1
-WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0;
+WHERE aggfnoid = 0
+ OR CASE WHEN aggisordsetfunc
+ THEN aggtransfn <> 0 OR aggfinalfn = 0
+ ELSE aggtransfn = 0 OR aggtranstype = 0
+ END;
ctid | aggfnoid
------+----------
(0 rows)
@@ -764,8 +772,9 @@ WHERE a.aggfnoid = p.oid AND
a.aggfinalfn = pfn.oid AND
(pfn.proretset
OR NOT binary_coercible(pfn.prorettype, p.prorettype)
- OR pfn.pronargs != 1
- OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]));
+ OR (aggisordsetfunc IS FALSE
+ AND (pfn.pronargs != 1
+ OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]))));
aggfnoid | proname | oid | proname
----------+---------+-----+---------
(0 rows)
@@ -857,10 +866,12 @@ ORDER BY 1;
count("any") | count()
(1 row)
--- For the same reason, we avoid creating built-in variadic aggregates.
-SELECT oid, proname
-FROM pg_proc AS p
-WHERE proisagg AND provariadic != 0;
+-- For the same reason, we avoid creating built-in variadic aggregates, except
+-- ordered set functions (which have their own syntax and are not subject to
+-- the misplaced ORDER BY issue).
+SELECT p.oid, proname
+FROM pg_proc AS p JOIN pg_aggregate AS a ON (a.aggfnoid=p.oid)
+WHERE proisagg AND provariadic != 0 AND NOT a.aggisordsetfunc;
oid | proname
-----+---------
(0 rows)
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 397edff..6e6a2db 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -481,6 +481,69 @@ select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
generate_series(1,2) i;
+-- ordered set functions
+
+select p, percentile_cont(p) within group (order by x::float8) from generate_series(1,5) x,
+ (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p)
+ group by p order by p;
+select p, percentile_cont(p order by p) within group (order by x::float8)
+ from generate_series(1,5) x, (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p)
+ group by p order by x;
+select p, sum() within group (order by x::float8)
+ from generate_series(1,5) x,
+ (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p;
+select p, percentile_cont(p,p) from generate_series(1,5) x,
+ (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p;
+select percentile_cont(0.5) within group (order by b) from aggtest;
+select percentile_cont(0.5) within group (order by b),sum(b) from aggtest;
+select percentile_cont(0.5) within group (order by thousand) from tenk1;
+select percentile_disc(0.5) within group (order by thousand) from tenk1;
+select rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+select cume_dist(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+select percent_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4),(5)) v(x);
+select dense_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+
+select percentile_disc(array[0,0.1,0.25,0.5,0.75,0.9,1]) within group (order by thousand) from tenk1;
+select percentile_cont(array[0,0.25,0.5,0.75,1]) within group (order by thousand) from tenk1;
+select percentile_disc(array[[null,1,0.5],[0.75,0.25,null]]) within group (order by thousand) from tenk1;
+select percentile_cont(array[0,1,0.25,0.75,0.5,1]) within group (order by x)
+ from generate_series(1,6) x;
+
+select ten, mode() within group (order by string4) from tenk1 group by ten;
+
+select percentile_disc(array[0.25,0.5,0.75]) within group (order by x)
+ from unnest('{fred,jim,fred,jack,jill,fred,jill,jim,jim,sheila,jim,sheila}'::text[]) u(x);
+
+-- ordered set funcs can't use ungrouped direct args:
+select rank(x) within group (order by x) from generate_series(1,5) x;
+
+-- collation:
+select pg_collation_for(percentile_disc(1) within group (order by x collate "POSIX"))
+ from (values ('fred'),('jim')) v(x);
+
+-- hypothetical type unification and argument failures:
+select rank(3) within group (order by x) from (values ('fred'),('jim')) v(x);
+select rank(3) within group (order by stringu1,stringu2) from tenk1;
+select rank('fred') within group (order by x) from generate_series(1,5) x;
+select rank('adam'::text collate "C") within group (order by x collate "POSIX")
+ from (values ('fred'),('jim')) v(x);
+-- hypothetical type unification successes:
+select rank('adam'::varchar) within group (order by x) from (values ('fred'),('jim')) v(x);
+select rank('3') within group (order by x) from generate_series(1,5) x;
+
+-- deparse and multiple features:
+create view aggordview1 as
+select ten,
+ percentile_disc(0.5) within group (order by thousand) as p50,
+ percentile_disc(0.5) within group (order by thousand) filter (where hundred=1) as px,
+ rank(5,'AZZZZ',50) within group (order by hundred, string4 desc, hundred)
+ from tenk1
+ group by ten order by ten;
+
+select pg_get_viewdef('aggordview1');
+select * from aggordview1 order by ten;
+drop view aggordview1;
+
-- variadic aggregates
select least_agg(q1,q2) from int8_tbl;
select least_agg(variadic array[q1,q2]) from int8_tbl;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index efcd70f..0b2cba7 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -564,10 +564,18 @@ SELECT * FROM funcdescs
-- **************** pg_aggregate ****************
-- Look for illegal values in pg_aggregate fields.
+-- ordered set functions can't have transfns, and must
+-- have finalfns, but may or may not have transtypes.
+-- other aggs must have transfns and transtypes with
+-- optional finalfns.
SELECT ctid, aggfnoid::oid
FROM pg_aggregate as p1
-WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0;
+WHERE aggfnoid = 0
+ OR CASE WHEN aggisordsetfunc
+ THEN aggtransfn <> 0 OR aggfinalfn = 0
+ ELSE aggtransfn = 0 OR aggtranstype = 0
+ END;
-- Make sure the matching pg_proc entry is sensible, too.
@@ -618,8 +626,9 @@ WHERE a.aggfnoid = p.oid AND
a.aggfinalfn = pfn.oid AND
(pfn.proretset
OR NOT binary_coercible(pfn.prorettype, p.prorettype)
- OR pfn.pronargs != 1
- OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]));
+ OR (aggisordsetfunc IS FALSE
+ AND (pfn.pronargs != 1
+ OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]))));
-- If transfn is strict then either initval should be non-NULL, or
-- input type should match transtype so that the first non-null input
@@ -685,11 +694,13 @@ WHERE p1.oid < p2.oid AND p1.proname = p2.proname AND
array_dims(p1.proargtypes) != array_dims(p2.proargtypes)
ORDER BY 1;
--- For the same reason, we avoid creating built-in variadic aggregates.
+-- For the same reason, we avoid creating built-in variadic aggregates, except
+-- ordered set functions (which have their own syntax and are not subject to
+-- the misplaced ORDER BY issue).
-SELECT oid, proname
-FROM pg_proc AS p
-WHERE proisagg AND provariadic != 0;
+SELECT p.oid, proname
+FROM pg_proc AS p JOIN pg_aggregate AS a ON (a.aggfnoid=p.oid)
+WHERE proisagg AND provariadic != 0 AND NOT a.aggisordsetfunc;
-- For the same reason, built-in aggregates with default arguments are no good.
On 11/04/2013 08:43 AM, Atri Sharma wrote:
Please find attached our latest version of the patch. This version
fixes the issues pointed out by the reviewers.
No, it doesn't. The documentation still contains formatting and
grammatical errors, and the code comments still do not match the their
surroundings. (The use of "I" in the code comments is a point I have
conceded on IRC, but I stand by my other remarks.)
Don't bother submitting a new patch until I've posted my technical
review, but please fix these issues on your local copy.
--
Vik
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi all,
Please find the latest version of the patch. This version fixes the
issues pointed out by the reviewer and the divide by zero bug in
percent_rank function. This version also adds a regression test for
the divide by zero case in percent_rank.
Regards,
Atri
Attachments:
withingroup14112013.patchtext/x-diff; charset=US-ASCII; name=withingroup14112013.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9388df5..0342cbe 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -352,7 +352,7 @@
<entry><structfield>aggtransfn</structfield></entry>
<entry><type>regproc</type></entry>
<entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry>Transition function</entry>
+ <entry>Transition function (zero if none)</entry>
</row>
<row>
<entry><structfield>aggfinalfn</structfield></entry>
@@ -370,7 +370,25 @@
<entry><structfield>aggtranstype</structfield></entry>
<entry><type>oid</type></entry>
<entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
- <entry>Data type of the aggregate function's internal transition (state) data</entry>
+ <entry>Data type of the aggregate function's internal transition (state) data (zero if none)</entry>
+ </row>
+ <row>
+ <entry><structfield>aggtranssortop</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.oid</literal></entry>
+ <entry>An optional sort operator for the type "aggtranstype", used for some kinds of ordered set functions</entry>
+ </row>
+ <row>
+ <entry><structfield>aggordnargs</structfield></entry>
+ <entry><type>int4</type></entry>
+ <entry></entry>
+ <entry>Number of direct arguments to ordered set function; -2 for hypothetical set functions; -1 for ordinary aggregates.</entry>
+ </row>
+ <row>
+ <entry><structfield>aggisordsetfunc</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>A flag to represent whether a function is ordered set or not</entry>
</row>
<row>
<entry><structfield>agginitval</structfield></entry>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 89f08af..5d76efe 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -12252,6 +12252,249 @@ SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab;
</sect1>
+ <sect1 id="functions-ordered">
+ <title>Ordered Set Functions</title>
+
+ <indexterm zone="functions-ordered">
+ <primary>ordered set function</primary>
+ <secondary>built-in</secondary>
+ </indexterm>
+
+ <para>
+ <firstterm>Ordered set functions</firstterm> compute a single result
+ from an ordered set of input values. The built-in ordered set functions
+ are listed in
+ <xref linkend="functions-inversedist-table"> and
+ <xref linkend="functions-hypothetical-table">.
+ The special syntax considerations for ordered set functions
+ are explained in <xref linkend="syntax-orderedset">.
+ </para>
+
+ <table id="functions-inversedist-table">
+ <title>Inverse Distribution Functions</title>
+
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Function</entry>
+ <entry>Direct Argument Type(s)</entry>
+ <entry>Ordered Argument Type(s)</entry>
+ <entry>Return Type</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>percentile</primary>
+ <secondary>discrete</secondary>
+ </indexterm>
+ <function>percentile_disc(<replaceable class="parameter">fraction</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>double precision</type> (must be [0..1])
+ </entry>
+ <entry>
+ any sortable type
+ </entry>
+ <entry>
+ same as sort expression
+ </entry>
+ <entry>
+ discrete percentile; returns the first result whose position in
+ the ordering equals or exceeds the specified fraction
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <function>percentile_disc(<replaceable class="parameter">fractions</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>double precision[]</type> (all must be [0..1] or null)
+ </entry>
+ <entry>
+ any sortable type
+ </entry>
+ <entry>
+ array of input type
+ </entry>
+ <entry>
+ multiple discrete percentile; returns an array of results matching the
+ shape of the <literal>fractions</literal> parameter, with each
+ non-null element replaced by the input value at that percentile
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>percentile</primary>
+ <secondary>continuous</secondary>
+ </indexterm>
+ <indexterm>
+ <primary>median</primary>
+ </indexterm>
+ <function>percentile_cont(<replaceable class="parameter">fraction</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>double precision</type> (must be [0..1])
+ </entry>
+ <entry>
+ <type>double precision</type> or <type>interval</type>
+ </entry>
+ <entry>
+ same as sort expression
+ </entry>
+ <entry>
+ continuous percentile; interpolates between adjacent items.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <function>percentile_cont(<replaceable class="parameter">fractions</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>double precision[]</type> (all must be [0..1] or null)
+ </entry>
+ <entry>
+ <type>double precision</type> or <type>interval</type>
+ </entry>
+ <entry>
+ array of input type
+ </entry>
+ <entry>
+ multiple continuous percentile; returns an array of results matching
+ the shape of the <literal>fractions</literal> parameter, with each
+ non-null element replaced by the value corresponding to that percentile
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>mode</primary>
+ <secondary>statistical</secondary>
+ </indexterm>
+ <function>mode() WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+ </entry>
+ <entry>
+ </entry>
+ <entry>
+ any sortable type
+ </entry>
+ <entry>
+ same as sort expression
+ </entry>
+ <entry>
+ returns the most frequent input value (choosing one arbitrarily if
+ there are multiple equally good results)
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ All the inverse distribution functions ignore null values in their sorted
+ input. The <replaceable>fraction</replaceable> parameter must be between 0
+ and 1; an error is thrown if not. However, a null fraction simply produces
+ a null result.
+ </para>
+
+ <table id="functions-hypothetical-table">
+ <title>Hypothetical Set Functions</title>
+
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Function</entry>
+ <entry>Return Type</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>rank</primary>
+ <secondary>hypothetical</secondary>
+ </indexterm>
+ <function>rank(<replaceable class="parameter">args</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sorted_args</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>bigint</type>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>dense_rank</primary>
+ <secondary>hypothetical</secondary>
+ </indexterm>
+ <function>dense_rank(<replaceable class="parameter">args</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sorted_args</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>bigint</type>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>percent_rank</primary>
+ <secondary>hypothetical</secondary>
+ </indexterm>
+ <function>percent_rank(<replaceable class="parameter">args</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sorted_args</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>double precision</type>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>cume_dist</primary>
+ <secondary>hypothetical</secondary>
+ </indexterm>
+ <function>cume_dist(<replaceable class="parameter">args</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sorted_args</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>double precision</type>
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ For all hypothetical set functions, the list of arguments given
+ by <replaceable>args</replaceable> should match the number and types of
+ arguments given as <replaceable>sorted_args</replaceable>.
+ </para>
+
+ <para>
+ All of the functions listed in
+ <xref linkend="functions-hypothetical-table"> are associated with a
+ window function defined in
+ <xref linkend="functions-window">. In each case, the function result
+ represents the value that the associated window function would have
+ returned, for the hypothetical row constructed from
+ <replaceable>args</replaceable> and included in the sorted group of
+ rows.
+ </para>
+
+ </sect1>
+
<sect1 id="functions-window">
<title>Window Functions</title>
diff --git a/doc/src/sgml/ref/alter_aggregate.sgml b/doc/src/sgml/ref/alter_aggregate.sgml
index aab5b2b..4fb3b6f 100644
--- a/doc/src/sgml/ref/alter_aggregate.sgml
+++ b/doc/src/sgml/ref/alter_aggregate.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
+ALTER AGGREGATE
RENAME TO <replaceable>new_name</replaceable>
ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
OWNER TO <replaceable>new_owner</replaceable>
ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
SET SCHEMA <replaceable>new_schema</replaceable>
+
+<phrase>where <replaceable>aggregate_signature</replaceable> is one of:</phrase>
+
+<replaceable>name</replaceable> ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
+<replaceable>name</replaceable> ( [ [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] ] ) WITHIN GROUP ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
</synopsis>
</refsynopsisdiv>
@@ -148,10 +153,11 @@ ALTER AGGREGATE myavg(integer) OWNER TO joe;
</para>
<para>
- To move the aggregate function <literal>myavg</literal> for type
- <type>integer</type> into schema <literal>myschema</literal>:
+ To move the ordered set function <literal>mypercentile</literal> with
+ direct argument of type <type>float8</type> taking groups
+ of <type>integer</type> type into schema <literal>myschema</literal>:
<programlisting>
-ALTER AGGREGATE myavg(integer) SET SCHEMA myschema;
+ALTER AGGREGATE mypercentile(float8) WITHIN GROUP (integer) SET SCHEMA myschema;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml
index a14fcb4..5d326ae 100644
--- a/doc/src/sgml/ref/alter_extension.sgml
+++ b/doc/src/sgml/ref/alter_extension.sgml
@@ -30,7 +30,7 @@ ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> DROP <replacea
<phrase>where <replaceable class="PARAMETER">member_object</replaceable> is:</phrase>
- AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) |
+ AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) [ WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) ] |
CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
CONVERSION <replaceable class="PARAMETER">object_name</replaceable> |
diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml
index e550500..c62fa44 100644
--- a/doc/src/sgml/ref/comment.sgml
+++ b/doc/src/sgml/ref/comment.sgml
@@ -23,7 +23,7 @@ PostgreSQL documentation
<synopsis>
COMMENT ON
{
- AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) |
+ AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) [ WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) ] |
CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
COLUMN <replaceable class="PARAMETER">relation_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
diff --git a/doc/src/sgml/ref/create_aggregate.sgml b/doc/src/sgml/ref/create_aggregate.sgml
index 2b35fa4..45b67c3 100644
--- a/doc/src/sgml/ref/create_aggregate.sgml
+++ b/doc/src/sgml/ref/create_aggregate.sgml
@@ -29,6 +29,15 @@ CREATE AGGREGATE <replaceable class="parameter">name</replaceable> ( [ <replacea
[ , SORTOP = <replaceable class="PARAMETER">sort_operator</replaceable> ]
)
+CREATE AGGREGATE <replaceable class="parameter">name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">arg_name</replaceable> ] <replaceable class="parameter">arg_data_type</replaceable> [ , ... ] ) WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">arg_name</replaceable> ] <replaceable class="parameter">arg_data_type</replaceable> [ , ... ] ) (
+ FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable>
+ [ , STRICT ]
+ [ , HYPOTHETICAL ]
+ [ , STYPE = <replaceable class="PARAMETER">state_data_type</replaceable> ]
+ [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
+ [ , TRANSSORTOP = <replaceable class="PARAMETER">state_sort_operator</replaceable> ]
+)
+
<phrase>or the old syntax</phrase>
CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
@@ -70,7 +79,7 @@ CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
</para>
<para>
- An aggregate function is made from one or two ordinary
+ An ordinary aggregate function is made from one or two ordinary
functions:
a state transition function
<replaceable class="PARAMETER">sfunc</replaceable>,
@@ -165,6 +174,14 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
</para>
<para>
+ The <literal>WITHIN GROUP</literal> syntax denotes a special subset of
+ aggregate functions collectively called <quote>ordered set
+ functions</quote>. These functions operate over groups of sorted values
+ in order-dependent ways. As such, they are constructed differently; there
+ is no state transition function, but the final function is required.
+ </para>
+
+ <para>
To be able to create an aggregate function, you must
have <literal>USAGE</literal> privilege on the argument types, the state
type, and the return type, as well as <literal>EXECUTE</literal> privilege
@@ -278,6 +295,11 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
aggregate's result, and the return type is <replaceable
class="PARAMETER">state_data_type</replaceable>.
</para>
+ <para>
+ For ordered set functions, the function arguments must instead
+ correspond to the input arguments (both direct and grouped) plus
+ the state type if any.
+ </para>
</listitem>
</varlistentry>
@@ -305,6 +327,39 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">state_sort_operator</replaceable></term>
+ <listitem>
+ <para>
+ For ordered set functions only, this is a sort operator that can be
+ applied to
+ the <replaceable class="PARAMETER">state_data_type</replaceable>.
+ This is just an operator name (possibly schema-qualified).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>STRICT</literal></term>
+ <listitem>
+ <para>
+ For ordered set functions only, this flag specifies that the function is
+ strict, i.e. that grouped rows containing nulls are skipped.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>HYPOTHETICAL</literal></term>
+ <listitem>
+ <para>
+ For ordered set functions only, this flag specifies that the aggregate
+ parameters are to be processed according to the requirements for
+ hypothetical set functions.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
<para>
diff --git a/doc/src/sgml/ref/drop_aggregate.sgml b/doc/src/sgml/ref/drop_aggregate.sgml
index 06060fb..2c5ab01 100644
--- a/doc/src/sgml/ref/drop_aggregate.sgml
+++ b/doc/src/sgml/ref/drop_aggregate.sgml
@@ -21,9 +21,13 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP AGGREGATE [ IF EXISTS ]
- <replaceable class="parameter">name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">arg_name</replaceable> ] <replaceable class="parameter">arg_data_type</replaceable> [ , ... ] )
+DROP AGGREGATE [ IF EXISTS ] <replaceable class="parameter">aggregate_signature</replaceable>
[ CASCADE | RESTRICT ]
+
+<phrase>where <replaceable>aggregate_signature</replaceable> is one of:</phrase>
+
+<replaceable>name</replaceable> ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
+<replaceable>name</replaceable> ( [ [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] ] ) WITHIN GROUP ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/security_label.sgml b/doc/src/sgml/ref/security_label.sgml
index 76c131f..ebca07b 100644
--- a/doc/src/sgml/ref/security_label.sgml
+++ b/doc/src/sgml/ref/security_label.sgml
@@ -25,7 +25,7 @@ SECURITY LABEL [ FOR <replaceable class="PARAMETER">provider</replaceable> ] ON
{
TABLE <replaceable class="PARAMETER">object_name</replaceable> |
COLUMN <replaceable class="PARAMETER">table_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
- AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) |
+ AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) [ WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) ] |
DATABASE <replaceable class="PARAMETER">object_name</replaceable> |
DOMAIN <replaceable class="PARAMETER">object_name</replaceable> |
EVENT TRIGGER <replaceable class="PARAMETER">object_name</replaceable> |
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 4f50f43..42f1a22 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -1706,6 +1706,54 @@ SELECT string_agg(a ORDER BY a, ',') FROM table; -- incorrect
</para>
</sect2>
+ <sect2 id="syntax-orderedset">
+ <title>Ordered Set Functions</title>
+
+ <indexterm zone="syntax-orderedset">
+ <primary>ordered set function</primary>
+ </indexterm>
+
+ <indexterm zone="syntax-orderedset">
+ <primary>aggregate function</primary>
+ <secondary>ordered set function</secondary>
+ </indexterm>
+
+ <indexterm zone="syntax-orderedset">
+ <primary>WITHIN GROUP</primary>
+ </indexterm>
+
+ <para>
+ An <firstterm>ordered set function</firstterm> is a particular kind of
+ aggregate function which is applied to sorted groups of values and returns
+ a single result for each group which may be influenced by the sort
+ order. Like all aggregate functions, it reduces multiple inputs to a
+ single output value; typical ordered set functions return a percentile
+ extracted from the ordered group, or the rank a specified value would have
+ within that group. The syntax of an ordered set function is:
+
+<synopsis>
+<replaceable>function_name</replaceable> ( [ <replaceable>expression</replaceable> [ , ... ] ] ) WITHIN GROUP ( <replaceable>order_by_clause</replaceable> ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+</synopsis>
+
+ where <replaceable>function_name</replaceable> is a previously
+ defined ordered set function (possibly qualified with a schema name) and
+ <replaceable>expression</replaceable> is any value expression that does
+ not itself contain an aggregate expression, a window function call, or any
+ reference to ungrouped columns of the source data. The
+ mandatory <replaceable>order_by_clause</replaceable> has the same syntax
+ as for a query-level <literal>ORDER BY</> clause, as described
+ in <xref linkend="queries-order">, except that its expressions are always
+ just expressions and cannot be output-column names or numbers. The
+ expressions of the <replaceable>order_by_clause</replaceable> may, and
+ almost invariably do, refer to the ungrouped columns of the input; the
+ clause defines the grouped input to the function. The optional
+ <replaceable>filter_clause</replaceable> is identical to that for
+ aggregate functions (see <xref linkend="syntax-aggregates">, and is applied
+ to input rows prior to the sort operation.
+ </para>
+
+ </sect2>
+
<sect2 id="syntax-window-functions">
<title>Window Function Calls</title>
diff --git a/doc/src/sgml/xaggr.sgml b/doc/src/sgml/xaggr.sgml
index 9ed7d99..fc51602 100644
--- a/doc/src/sgml/xaggr.sgml
+++ b/doc/src/sgml/xaggr.sgml
@@ -9,20 +9,17 @@
</indexterm>
<para>
- Aggregate functions in <productname>PostgreSQL</productname>
- are expressed in terms of <firstterm>state values</firstterm>
- and <firstterm>state transition functions</firstterm>.
- That is, an aggregate operates using a state value that is updated
- as each successive input row is processed.
- To define a new aggregate
- function, one selects a data type for the state value,
- an initial value for the state, and a state transition
- function. The state transition function is just an
- ordinary function that could also be used outside the
- context of the aggregate. A <firstterm>final function</firstterm>
- can also be specified, in case the desired result of the aggregate
- is different from the data that needs to be kept in the running
- state value.
+ Aggregate functions (other than ordered set functions)
+ in <productname>PostgreSQL</productname> are expressed in terms
+ of <firstterm>state values</firstterm> and <firstterm>state transition
+ functions</firstterm>. That is, an aggregate operates using a state value
+ that is updated as each successive input row is processed. To define a new
+ aggregate function, one selects a data type for the state value, an initial
+ value for the state, and a state transition function. The state transition
+ function is just an ordinary function that could also be used outside the
+ context of the aggregate. A <firstterm>final function</firstterm> can also
+ be specified, in case the desired result of the aggregate is different from
+ the data that needs to be kept in the running state value.
</para>
<para>
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index d9e961e..044e667 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -46,6 +46,7 @@ Oid
AggregateCreate(const char *aggName,
Oid aggNamespace,
int numArgs,
+ int numDirectArgs,
oidvector *parameterTypes,
Datum allParameterTypes,
Datum parameterModes,
@@ -54,24 +55,29 @@ AggregateCreate(const char *aggName,
List *aggtransfnName,
List *aggfinalfnName,
List *aggsortopName,
+ List *aggtranssortopName,
Oid aggTransType,
- const char *agginitval)
+ const char *agginitval,
+ bool isStrict,
+ bool isOrderedSet,
+ bool isHypotheticalSet)
{
Relation aggdesc;
HeapTuple tup;
bool nulls[Natts_pg_aggregate];
Datum values[Natts_pg_aggregate];
Form_pg_proc proc;
- Oid transfn;
- Oid finalfn = InvalidOid; /* can be omitted */
- Oid sortop = InvalidOid; /* can be omitted */
+ Oid transfn = InvalidOid; /* can be omitted */
+ Oid finalfn = InvalidOid; /* can be omitted */
+ Oid sortop = InvalidOid; /* can be omitted */
+ Oid transsortop = InvalidOid; /* can be omitted */
Oid *aggArgTypes = parameterTypes->values;
bool hasPolyArg;
bool hasInternalArg;
+ Oid variadic_type = InvalidOid;
Oid rettype;
Oid finaltype;
- Oid *fnArgs;
- int nargs_transfn;
+ Oid *fnArgs = palloc((numArgs + 1) * sizeof(Oid));
Oid procOid;
TupleDesc tupDesc;
int i;
@@ -83,8 +89,20 @@ AggregateCreate(const char *aggName,
if (!aggName)
elog(ERROR, "no aggregate name supplied");
- if (!aggtransfnName)
- elog(ERROR, "aggregate must have a transition function");
+ if (isOrderedSet)
+ {
+ if (aggtransfnName)
+ elog(ERROR, "ordered set functions cannot have transition functions");
+ if (!aggfinalfnName)
+ elog(ERROR, "ordered set functions must have final functions");
+ }
+ else
+ {
+ if (!aggtransfnName)
+ elog(ERROR, "aggregate must have a transition function");
+ if (isStrict)
+ elog(ERROR, "aggregate with transition function must not be explicitly STRICT");
+ }
/* check for polymorphic and INTERNAL arguments */
hasPolyArg = false;
@@ -97,6 +115,136 @@ AggregateCreate(const char *aggName,
hasInternalArg = true;
}
+ /*-
+ * Argument mode checks. If there were no variadics, we should have been
+ * passed a NULL pointer for parameterModes, so we can skip this if so.
+ * Otherwise, the allowed cases are as follows:
+ *
+ * aggfn(..., variadic sometype) - normal agg with variadic arg last
+ * aggfn(..., variadic "any") - normal agg with "any" variadic
+ *
+ * ordfn(..., variadic "any") within group (*)
+ * - ordered set func with "any" variadic in direct args, which requires
+ * that the ordered args also be variadic any which we represent
+ * specially; this is the common case for hypothetical set functions.
+ * Note this is the only case where numDirectArgs == numArgs on input
+ * (implies finalfn(..., variadic "any"))
+ *
+ * ordfn(...) within group (..., variadic "any")
+ * - ordered set func with no variadic in direct args, but allowing any
+ * types of ordered args.
+ * (implies finalfn(..., ..., variadic "any"))
+ *
+ * We don't allow variadic ordered args other than "any"; we don't allow
+ * anything after variadic "any" except the special-case (*).
+ *
+ * We might like to support this one:
+ *
+ * ordfn(..., variadic sometype) within group (...)
+ * - ordered set func with variadic direct arg last, followed by ordered
+ * args, none of which are variadic
+ * (implies finalfn(..., sometype, ..., [transtype]))
+ *
+ * but currently it seems to be too intrusive to do so; the assumption
+ * that variadic args can only come last is quite widespread.
+ */
+
+ if (parameterModes != PointerGetDatum(NULL))
+ {
+ /*
+ * We expect the array to be a 1-D CHAR array; verify that. We don't
+ * need to use deconstruct_array() since the array data is just going
+ * to look like a C array of char values.
+ */
+ ArrayType *modesArray = (ArrayType *) DatumGetPointer(parameterModes);
+ char *paramModes;
+ int modesCount;
+ int i;
+
+ if (ARR_NDIM(modesArray) != 1 ||
+ ARR_HASNULL(modesArray) ||
+ ARR_ELEMTYPE(modesArray) != CHAROID)
+ elog(ERROR, "parameterModes is not a 1-D char array");
+
+ paramModes = (char *) ARR_DATA_PTR(modesArray);
+ modesCount = ARR_DIMS(modesArray)[0];
+
+ for (i = 0; i < modesCount; ++i)
+ {
+ switch (paramModes[i])
+ {
+ case PROARGMODE_VARIADIC:
+ if (OidIsValid(variadic_type))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("VARIADIC must not be specified more than once")));
+ variadic_type = aggArgTypes[i];
+
+ /* enforce restrictions on ordered args */
+
+ if (numDirectArgs >= 0
+ && i >= numDirectArgs
+ && variadic_type != ANYOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("VARIADIC ordered arguments must be of type ANY")));
+
+ break;
+
+ case PROARGMODE_IN:
+ if (OidIsValid(variadic_type))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("VARIADIC argument must be last")));
+ break;
+
+ default:
+ elog(ERROR, "invalid argument mode");
+ }
+ }
+ }
+
+ switch (variadic_type)
+ {
+ case InvalidOid:
+ case ANYARRAYOID:
+ case ANYOID:
+ /* okay */
+ break;
+ default:
+ if (!OidIsValid(get_element_type(variadic_type)))
+ elog(ERROR, "VARIADIC parameter must be an array");
+ break;
+ }
+
+ if (isHypotheticalSet)
+ {
+ if (numArgs != numDirectArgs
+ || variadic_type != ANYOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("invalid argument types for hypothetical set function"),
+ errhint("Required declaration is (..., VARIADIC \"any\") WITHIN GROUP (*)")));
+
+ /* flag for special processing for hypothetical sets */
+ numDirectArgs = -2;
+ }
+ else if (numArgs == numDirectArgs)
+ {
+ if (variadic_type == ANYOID)
+ {
+ /*
+ * this case allows the number of direct args to be truly variable
+ */
+ numDirectArgs = -1;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("invalid argument types for ordered set function"),
+ errhint("WITHIN GROUP (*) is not allowed without VARIADIC \"any\"")));
+ }
+
/*
* If transtype is polymorphic, must have polymorphic argument also; else
* we will have no way to deduce the actual transtype.
@@ -107,53 +255,86 @@ AggregateCreate(const char *aggName,
errmsg("cannot determine transition data type"),
errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument.")));
- /* find the transfn */
- nargs_transfn = numArgs + 1;
- fnArgs = (Oid *) palloc(nargs_transfn * sizeof(Oid));
- fnArgs[0] = aggTransType;
- memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
- transfn = lookup_agg_function(aggtransfnName, nargs_transfn, fnArgs,
- &rettype);
+ if (!isOrderedSet)
+ {
+ /* find the transfn */
- /*
- * Return type of transfn (possibly after refinement by
- * enforce_generic_type_consistency, if transtype isn't polymorphic) must
- * exactly match declared transtype.
- *
- * In the non-polymorphic-transtype case, it might be okay to allow a
- * rettype that's binary-coercible to transtype, but I'm not quite
- * convinced that it's either safe or useful. When transtype is
- * polymorphic we *must* demand exact equality.
- */
- if (rettype != aggTransType)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("return type of transition function %s is not %s",
- NameListToString(aggtransfnName),
- format_type_be(aggTransType))));
+ fnArgs[0] = aggTransType;
+ memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
- tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn));
- if (!HeapTupleIsValid(tup))
- elog(ERROR, "cache lookup failed for function %u", transfn);
- proc = (Form_pg_proc) GETSTRUCT(tup);
+ transfn = lookup_agg_function(aggtransfnName, numArgs + 1, fnArgs,
+ &rettype);
- /*
- * If the transfn is strict and the initval is NULL, make sure first input
- * type and transtype are the same (or at least binary-compatible), so
- * that it's OK to use the first input value as the initial transValue.
- */
- if (proc->proisstrict && agginitval == NULL)
- {
- if (numArgs < 1 ||
- !IsBinaryCoercible(aggArgTypes[0], aggTransType))
+ /*
+ * Return type of transfn (possibly after refinement by
+ * enforce_generic_type_consistency, if transtype isn't polymorphic)
+ * must exactly match declared transtype.
+ *
+ * In the non-polymorphic-transtype case, it might be okay to allow a
+ * rettype that's binary-coercible to transtype, but I'm not quite
+ * convinced that it's either safe or useful. When transtype is
+ * polymorphic we *must* demand exact equality.
+ */
+ if (rettype != aggTransType)
ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("return type of transition function %s is not %s",
+ NameListToString(aggtransfnName),
+ format_type_be(aggTransType))));
+
+ tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for function %u", transfn);
+ proc = (Form_pg_proc) GETSTRUCT(tup);
+
+ /*
+ * If the transfn is strict and the initval is NULL, make sure first
+ * input type and transtype are the same (or at least
+ * binary-compatible), so that it's OK to use the first input value as
+ * the initial transValue.
+ */
+ if (proc->proisstrict && agginitval == NULL)
+ {
+ if (numArgs < 1 ||
+ !IsBinaryCoercible(aggArgTypes[0], aggTransType))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
+ }
+ ReleaseSysCache(tup);
}
- ReleaseSysCache(tup);
/* handle finalfn, if supplied */
- if (aggfinalfnName)
+ if (isOrderedSet)
+ {
+ int num_final_args = numArgs;
+
+ memcpy(fnArgs, aggArgTypes, num_final_args * sizeof(Oid));
+
+ /*
+ * If there's a transtype, it becomes the last arg to the finalfn;
+ * but if the agg (and hence the finalfn) is variadic "any", then
+ * this contributes nothing to the signature.
+ */
+ if (aggTransType != InvalidOid && variadic_type != ANYOID)
+ fnArgs[num_final_args++] = aggTransType;
+
+ finalfn = lookup_agg_function(aggfinalfnName, num_final_args, fnArgs,
+ &finaltype);
+
+ /*
+ * This is also checked at runtime for security reasons, but check
+ * here too to provide a friendly error (the requirement is because
+ * the finalfn will be passed null dummy args for type resolution
+ * purposes).
+ */
+
+ if (func_strict(finalfn))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("ordered set final functions must not be declared STRICT")));
+ }
+ else if (aggfinalfnName)
{
fnArgs[0] = aggTransType;
finalfn = lookup_agg_function(aggfinalfnName, 1, fnArgs,
@@ -166,6 +347,7 @@ AggregateCreate(const char *aggName,
*/
finaltype = aggTransType;
}
+
Assert(OidIsValid(finaltype));
/*
@@ -207,6 +389,18 @@ AggregateCreate(const char *aggName,
false, -1);
}
+ /* handle transsortop, if supplied */
+ if (aggtranssortopName)
+ {
+ if (!isOrderedSet || !OidIsValid(aggTransType))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("transition sort operator can only be specified for ordered set functions with transition types")));
+ transsortop = LookupOperName(NULL, aggtranssortopName,
+ aggTransType, aggTransType,
+ false, -1);
+ }
+
/*
* permission checks on used types
*/
@@ -217,15 +411,17 @@ AggregateCreate(const char *aggName,
aclcheck_error_type(aclresult, aggArgTypes[i]);
}
- aclresult = pg_type_aclcheck(aggTransType, GetUserId(), ACL_USAGE);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error_type(aclresult, aggTransType);
+ if (OidIsValid(aggTransType))
+ {
+ aclresult = pg_type_aclcheck(aggTransType, GetUserId(), ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error_type(aclresult, aggTransType);
+ }
aclresult = pg_type_aclcheck(finaltype, GetUserId(), ACL_USAGE);
if (aclresult != ACLCHECK_OK)
aclcheck_error_type(aclresult, finaltype);
-
/*
* Everything looks okay. Try to create the pg_proc entry for the
* aggregate. (This could fail if there's already a conflicting entry.)
@@ -246,7 +442,7 @@ AggregateCreate(const char *aggName,
false, /* security invoker (currently not
* definable for agg) */
false, /* isLeakProof */
- false, /* isStrict (not needed for agg) */
+ isStrict, /* isStrict (needed for ordered set funcs) */
PROVOLATILE_IMMUTABLE, /* volatility (not
* needed for agg) */
parameterTypes, /* paramTypes */
@@ -272,7 +468,11 @@ AggregateCreate(const char *aggName,
values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
+ values[Anum_pg_aggregate_aggtranssortop - 1] = ObjectIdGetDatum(transsortop);
values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
+ values[Anum_pg_aggregate_aggordnargs - 1] = Int32GetDatum(numDirectArgs);
+ values[Anum_pg_aggregate_aggisordsetfunc - 1] = BoolGetDatum(isOrderedSet);
+
if (agginitval)
values[Anum_pg_aggregate_agginitval - 1] = CStringGetTextDatum(agginitval);
else
@@ -290,18 +490,23 @@ AggregateCreate(const char *aggName,
/*
* Create dependencies for the aggregate (above and beyond those already
- * made by ProcedureCreate). Note: we don't need an explicit dependency
- * on aggTransType since we depend on it indirectly through transfn.
+ * made by ProcedureCreate). Normal aggs don't need an explicit
+ * dependency on aggTransType since we depend on it indirectly through
+ * transfn, but ordered set functions with variadic "any" do need one
+ * (ordered set functions without variadic depend on it via the finalfn).
*/
myself.classId = ProcedureRelationId;
myself.objectId = procOid;
myself.objectSubId = 0;
/* Depends on transition function */
- referenced.classId = ProcedureRelationId;
- referenced.objectId = transfn;
- referenced.objectSubId = 0;
- recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ if (OidIsValid(transfn))
+ {
+ referenced.classId = ProcedureRelationId;
+ referenced.objectId = transfn;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
/* Depends on final function, if any */
if (OidIsValid(finalfn))
@@ -321,6 +526,24 @@ AggregateCreate(const char *aggName,
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
+ /* Depends on transsort operator, if any */
+ if (OidIsValid(transsortop))
+ {
+ referenced.classId = OperatorRelationId;
+ referenced.objectId = transsortop;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /* May depend on aggTransType if any */
+ if (OidIsValid(aggTransType) && isOrderedSet && variadic_type == ANYOID)
+ {
+ referenced.classId = TypeRelationId;
+ referenced.objectId = aggTransType;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
return procOid;
}
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 78af092..9774667 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -44,9 +44,12 @@
* DefineAggregate
*
* "oldstyle" signals the old (pre-8.2) style where the aggregate input type
- * is specified by a BASETYPE element in the parameters. Otherwise,
- * "args" is a list of FunctionParameter structs defining the agg's arguments.
- * "parameters" is a list of DefElem representing the agg's definition clauses.
+ * is specified by a BASETYPE element in the parameters. Otherwise, "args" is
+ * a pair, whose first element is a list of FunctionParameter structs defining
+ * the agg's arguments (both direct and ordered), and whose second element is
+ * an Integer node with the number of direct args, or -1 if this isn't an
+ * ordered set func. "parameters" is a list of DefElem representing the agg's
+ * definition clauses.
*/
Oid
DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
@@ -58,18 +61,23 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
List *transfuncName = NIL;
List *finalfuncName = NIL;
List *sortoperatorName = NIL;
+ List *transsortoperatorName = NIL;
TypeName *baseType = NULL;
TypeName *transType = NULL;
char *initval = NULL;
int numArgs;
+ int numDirectArgs = -1;
+ Oid transTypeId = InvalidOid;
oidvector *parameterTypes;
ArrayType *allParameterTypes;
ArrayType *parameterModes;
ArrayType *parameterNames;
List *parameterDefaults;
- Oid transTypeId;
char transTypeType;
ListCell *pl;
+ bool ishypothetical = false;
+ bool isOrderedSet = false;
+ bool isStrict = false;
/* Convert list of names to a name and namespace */
aggNamespace = QualifiedNameGetCreationNamespace(name, &aggName);
@@ -80,6 +88,14 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
get_namespace_name(aggNamespace));
+ Assert(args == NIL || list_length(args) == 2);
+
+ if (list_length(args) == 2)
+ {
+ numDirectArgs = intVal(lsecond(args));
+ isOrderedSet = (numDirectArgs != -1);
+ }
+
foreach(pl, parameters)
{
DefElem *defel = (DefElem *) lfirst(pl);
@@ -106,6 +122,12 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
initval = defGetString(defel);
else if (pg_strcasecmp(defel->defname, "initcond1") == 0)
initval = defGetString(defel);
+ else if (pg_strcasecmp(defel->defname, "hypothetical") == 0)
+ ishypothetical = true;
+ else if (pg_strcasecmp(defel->defname, "strict") == 0)
+ isStrict = true;
+ else if (pg_strcasecmp(defel->defname, "transsortop") == 0)
+ transsortoperatorName = defGetQualifiedName(defel);
else
ereport(WARNING,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -113,17 +135,35 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
defel->defname)));
}
- /*
- * make sure we have our required definitions
- */
- if (transType == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("aggregate stype must be specified")));
- if (transfuncName == NIL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("aggregate sfunc must be specified")));
+ if (!isOrderedSet)
+ {
+ /*
+ * make sure we have our required definitions
+ */
+ if (transType == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("aggregate stype must be specified")));
+ if (transfuncName == NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("aggregate sfunc must be specified")));
+ if (isStrict)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("aggregate with sfunc must not be explicitly declared STRICT")));
+ }
+ else
+ {
+ if (transfuncName != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("sfunc must not be specified for ordered set functions")));
+ if (finalfuncName == NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("finalfunc must be specified for ordered set functions")));
+ }
/*
* look up the aggregate's input datatype(s).
@@ -173,8 +213,15 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("basetype is redundant with aggregate input type specification")));
- numArgs = list_length(args);
- interpret_function_parameter_list(args,
+ /*
+ * The grammar has already concatenated the direct and ordered
+ * args (if any) for us. Note that error checking for position
+ * and number of VARIADIC args is not done for us, we have to
+ * do it ourselves later (in AggregateCreate)
+ */
+
+ numArgs = list_length(linitial(args));
+ interpret_function_parameter_list(linitial(args),
InvalidOid,
true, /* is an aggregate */
queryString,
@@ -191,7 +238,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
}
/*
- * look up the aggregate's transtype.
+ * look up the aggregate's transtype, if specified.
*
* transtype can't be a pseudo-type, since we need to be able to store
* values of the transtype. However, we can allow polymorphic transtype
@@ -201,18 +248,20 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
* worse) by connecting up incompatible internal-using functions in an
* aggregate.
*/
- transTypeId = typenameTypeId(NULL, transType);
- transTypeType = get_typtype(transTypeId);
- if (transTypeType == TYPTYPE_PSEUDO &&
- !IsPolymorphicType(transTypeId))
+ if (transType)
{
- if (transTypeId == INTERNALOID && superuser())
- /* okay */ ;
- else
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("aggregate transition data type cannot be %s",
- format_type_be(transTypeId))));
+ transTypeId = typenameTypeId(NULL, transType);
+ transTypeType = get_typtype(transTypeId);
+ if (transTypeType == TYPTYPE_PSEUDO &&
+ !IsPolymorphicType(transTypeId))
+ {
+ if (transTypeId != INTERNALOID || !superuser() || isOrderedSet)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("aggregate transition data type cannot be %s",
+ format_type_be(transTypeId))));
+ }
+
}
/*
@@ -224,13 +273,23 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
* value. However, if it's an incorrect value it seems much more
* user-friendly to complain at CREATE AGGREGATE time.
*/
- if (initval && transTypeType != TYPTYPE_PSEUDO)
+ if (transType)
{
- Oid typinput,
- typioparam;
+ if (initval && transTypeType != TYPTYPE_PSEUDO)
+ {
+ Oid typinput,
+ typioparam;
- getTypeInputInfo(transTypeId, &typinput, &typioparam);
- (void) OidInputFunctionCall(typinput, initval, typioparam, -1);
+ getTypeInputInfo(transTypeId, &typinput, &typioparam);
+ (void) OidInputFunctionCall(typinput, initval, typioparam, -1);
+ }
+ }
+ else
+ {
+ if (initval)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("INITVAL must not be specified without STYPE")));
}
/*
@@ -239,6 +298,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
return AggregateCreate(aggName, /* aggregate name */
aggNamespace, /* namespace */
numArgs,
+ numDirectArgs,
parameterTypes,
PointerGetDatum(allParameterTypes),
PointerGetDatum(parameterModes),
@@ -247,6 +307,10 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
transfuncName, /* step function name */
finalfuncName, /* final function name */
sortoperatorName, /* sort operator name */
+ transsortoperatorName, /* transsort operator name */
transTypeId, /* transition data type */
- initval); /* initial condition */
+ initval, /* initial condition */
+ isStrict, /* is explicitly STRICT */
+ isOrderedSet, /* If the function is an ordered set */
+ ishypothetical); /* If the function is a hypothetical set */
}
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index ca754b4..2399446 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -274,8 +274,13 @@ interpret_function_parameter_list(List *parameters,
/* handle input parameters */
if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
{
- /* other input parameters can't follow a VARIADIC parameter */
- if (varCount > 0)
+ /*
+ * For functions, other input parameters can't follow a VARIADIC
+ * parameter; for aggregates, we might be dealing with an ordered
+ * set function which have more complex rules for variadics, so
+ * punt the error checking for that case to the caller.
+ */
+ if (varCount > 0 && !is_aggregate)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("VARIADIC parameter must be the last input parameter")));
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 90c2753..e6fb8b0 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -4410,6 +4410,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
parent);
+ astate->orddirectargs = (List *) ExecInitExpr((Expr *) aggref->orddirectargs, parent);
astate->aggfilter = ExecInitExpr(aggref->aggfilter,
parent);
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index ff6a123..f216cd4 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -380,8 +380,8 @@ sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
param = ParseFuncOrColumn(pstate,
list_make1(subfield),
list_make1(param),
- NIL, NULL, false, false, false,
- NULL, true, cref->location);
+ cref->location,
+ NULL);
}
return param;
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index e02a6ff..3f2cf8d 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -3,7 +3,7 @@
* nodeAgg.c
* Routines to handle aggregate nodes.
*
- * ExecAgg evaluates each aggregate in the following steps:
+ * ExecAgg evaluates each normal aggregate in the following steps:
*
* transvalue = initcond
* foreach input_tuple do
@@ -66,6 +66,26 @@
* AggState is available as context in earlier releases (back to 8.1),
* but direct examination of the node is needed to use it before 9.0.
*
+ *---
+ *
+ * Ordered set functions modify the above process in a number of ways.
+ * Most importantly, they do not have transfuncs at all; the same sort
+ * mechanism used for ORDER BY/DISTINCT as described above is used to
+ * process the input, but then the finalfunc is called without actually
+ * running the sort (the finalfunc is allowed to insert rows first).
+ * The finalfunc has access via a set of AggSet* API functions to the
+ * Tuplesortstate, row count in the group, and other ancillary info.
+ *
+ * Ordered set functions can, however, have a transvalue declared; this is
+ * treated as a constant, and added to the end of the sort fields.
+ * Hypothetical set functions use this to provide a flag that distinguishes
+ * the hypothetical row from the input data.
+ *
+ * Since they have no transfunc, ordered set functions have their own
+ * 'strict' flag stored in the aggregate's own pg_proc entry; this affects
+ * whether rows containing nulls are placed in the sorter. But since we
+ * pass dummy null arguments to the finalfunc for type resolution purposes,
+ * no ordered set finalfunc is allowed to be strict.
*
* Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -87,10 +107,12 @@
#include "executor/nodeAgg.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+#include "nodes/makefuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/tlist.h"
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
+#include "parser/parse_clause.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
@@ -105,6 +127,8 @@
*/
typedef struct AggStatePerAggData
{
+ NodeTag type;
+
/*
* These values are set up during ExecInitAgg() and do not change
* thereafter:
@@ -114,10 +138,25 @@ typedef struct AggStatePerAggData
AggrefExprState *aggrefstate;
Aggref *aggref;
- /* number of input arguments for aggregate function proper */
+ /* Pointer to parent AggState node */
+ AggState *aggstate;
+
+ /* copied from aggref */
+ bool isOrderedSet;
+
+ /*
+ * number of arguments for aggregate function proper.
+ * For ordered set functions, this includes the ORDER BY
+ * columns, *except* in the case of hypothetical set functions.
+ */
int numArguments;
- /* number of inputs including ORDER BY expressions */
+ /*
+ * number of inputs including ORDER BY expressions. For ordered
+ * set functions, *only* the ORDER BY expressions are included
+ * here, since the direct args to the function are not properly
+ * "input" in the sense of being derived from the tuple group.
+ */
int numInputs;
/* Oids of transfer functions */
@@ -126,12 +165,23 @@ typedef struct AggStatePerAggData
/*
* fmgr lookup data for transfer functions --- only valid when
- * corresponding oid is not InvalidOid. Note in particular that fn_strict
- * flags are kept here.
+ * corresponding oid is not InvalidOid.
*/
FmgrInfo transfn;
FmgrInfo finalfn;
+ /*
+ * If >0, aggregate as a whole is strict (skips null input)
+ * The value specifies how many columns to check; normal aggs
+ * only check numArguments, while ordered set functions check
+ * numInputs.
+ *
+ * Ordered set functions are not allowed to have strict finalfns;
+ * other aggregates respect the finalfn strict flag in the
+ * FmgrInfo above.
+ */
+ int numStrict;
+
/* Input collation derived for aggregate */
Oid aggCollation;
@@ -148,6 +198,9 @@ typedef struct AggStatePerAggData
Oid *sortCollations;
bool *sortNullsFirst;
+ /* just for convenience of ordered set funcs, not used here */
+ Oid *sortEqOperators;
+
/*
* fmgr lookup data for input columns' equality operators --- only
* set/used when aggregate has DISTINCT flag. Note that these are in
@@ -204,6 +257,9 @@ typedef struct AggStatePerAggData
*/
Tuplesortstate *sortstate; /* sort object, if DISTINCT or ORDER BY */
+
+ int64 number_of_rows; /* number of rows */
+
} AggStatePerAggData;
/*
@@ -300,6 +356,8 @@ initialize_aggregates(AggState *aggstate,
AggStatePerAgg peraggstate = &peragg[aggno];
AggStatePerGroup pergroupstate = &pergroup[aggno];
+ peraggstate->number_of_rows = 0;
+
/*
* Start a fresh sort operation for each DISTINCT/ORDER BY aggregate.
*/
@@ -383,14 +441,17 @@ advance_transition_function(AggState *aggstate,
MemoryContext oldContext;
Datum newVal;
int i;
+ int numStrict = peraggstate->numStrict;
- if (peraggstate->transfn.fn_strict)
+ Assert(OidIsValid(peraggstate->transfn_oid));
+
+ if (numStrict > 0)
{
/*
* For a strict transfn, nothing happens when there's a NULL input; we
* just keep the prior transValue.
*/
- for (i = 1; i <= numArguments; i++)
+ for (i = 1; i <= numStrict; i++)
{
if (fcinfo->argnull[i])
return;
@@ -506,24 +567,24 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
if (peraggstate->numSortCols > 0)
{
+ int numStrict = peraggstate->numStrict;
+
/* DISTINCT and/or ORDER BY case */
Assert(slot->tts_nvalid == peraggstate->numInputs);
/*
- * If the transfn is strict, we want to check for nullity before
+ * If the aggregate is strict, we want to check for nullity before
* storing the row in the sorter, to save space if there are a lot
- * of nulls. Note that we must only check numArguments columns,
- * not numInputs, since nullity in columns used only for sorting
- * is not relevant here.
+ * of nulls.
*/
- if (peraggstate->transfn.fn_strict)
+ if (numStrict > 0)
{
- for (i = 0; i < nargs; i++)
+ for (i = 0; i < numStrict; i++)
{
if (slot->tts_isnull[i])
break;
}
- if (i < nargs)
+ if (i < numStrict)
continue;
}
@@ -534,6 +595,8 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
slot->tts_isnull[0]);
else
tuplesort_puttupleslot(peraggstate->sortstate, slot);
+
+ peraggstate->number_of_rows++;
}
else
{
@@ -756,15 +819,66 @@ finalize_aggregate(AggState *aggstate,
if (OidIsValid(peraggstate->finalfn_oid))
{
FunctionCallInfoData fcinfo;
+ bool isnull = false;
+
+ if (!(peraggstate->isOrderedSet))
+ {
+ InitFunctionCallInfoData(fcinfo,
+ &(peraggstate->finalfn),
+ 1,
+ peraggstate->aggCollation,
+ (void *) aggstate,
+ NULL);
+
+ fcinfo.arg[0] = pergroupstate->transValue;
+ fcinfo.argnull[0] = isnull = pergroupstate->transValueIsNull;
+ }
+ else
+ {
+ List *args = peraggstate->aggrefstate->orddirectargs;
+ ListCell *lc;
+ int i = 0;
+ int numArguments = peraggstate->numArguments;
+
+ ExecClearTuple(peraggstate->evalslot);
+ ExecClearTuple(peraggstate->uniqslot);
+
+ InitFunctionCallInfoData(fcinfo,
+ &(peraggstate->finalfn),
+ peraggstate->numArguments,
+ peraggstate->aggCollation,
+ (void *) peraggstate,
+ NULL);
+
+ foreach (lc, args)
+ {
+ ExprState *expr = (ExprState *) lfirst(lc);
+
+ fcinfo.arg[i] = ExecEvalExpr(expr,
+ aggstate->ss.ps.ps_ExprContext,
+ &fcinfo.argnull[i],
+ NULL);
+ if (fcinfo.argnull[i])
+ isnull = true;
- InitFunctionCallInfoData(fcinfo, &(peraggstate->finalfn), 1,
- peraggstate->aggCollation,
- (void *) aggstate, NULL);
- fcinfo.arg[0] = pergroupstate->transValue;
- fcinfo.argnull[0] = pergroupstate->transValueIsNull;
- if (fcinfo.flinfo->fn_strict && pergroupstate->transValueIsNull)
+ ++i;
+ }
+
+ for(; i < numArguments; i++)
+ {
+ fcinfo.arg[i] = (Datum) 0;
+ fcinfo.argnull[i] = true;
+ isnull = true;
+ }
+ }
+
+ if (isnull && fcinfo.flinfo->fn_strict)
{
- /* don't call a strict function with NULL inputs */
+ /*
+ * don't call a strict function with NULL inputs; for ordered set
+ * functions this is paranoia, we already required that fn_strict
+ * is false, but easy to check anyway
+ */
*resultVal = (Datum) 0;
*resultIsNull = true;
}
@@ -1164,6 +1278,17 @@ agg_retrieve_direct(AggState *aggstate)
}
/*
+ * Use the representative input tuple for any references to
+ * non-aggregated input columns in the qual and tlist. (If we are not
+ * grouping, and there are no input rows at all, we will come here
+ * with an empty firstSlot ... but if not grouping, there can't be any
+ * references to non-aggregated input columns, so no problem.)
+ * We do this before finalizing because for ordered set functions,
+ * finalize_aggregates can evaluate arguments referencing the tuple.
+ */
+ econtext->ecxt_outertuple = firstSlot;
+
+ /*
* Done scanning input tuple group. Finalize each aggregate
* calculation, and stash results in the per-output-tuple context.
*/
@@ -1174,14 +1299,17 @@ agg_retrieve_direct(AggState *aggstate)
if (peraggstate->numSortCols > 0)
{
- if (peraggstate->numInputs == 1)
- process_ordered_aggregate_single(aggstate,
- peraggstate,
- pergroupstate);
- else
- process_ordered_aggregate_multi(aggstate,
- peraggstate,
- pergroupstate);
+ if (!(peraggstate->isOrderedSet))
+ {
+ if (peraggstate->numInputs == 1)
+ process_ordered_aggregate_single(aggstate,
+ peraggstate,
+ pergroupstate);
+ else
+ process_ordered_aggregate_multi(aggstate,
+ peraggstate,
+ pergroupstate);
+ }
}
finalize_aggregate(aggstate, peraggstate, pergroupstate,
@@ -1189,15 +1317,6 @@ agg_retrieve_direct(AggState *aggstate)
}
/*
- * Use the representative input tuple for any references to
- * non-aggregated input columns in the qual and tlist. (If we are not
- * grouping, and there are no input rows at all, we will come here
- * with an empty firstSlot ... but if not grouping, there can't be any
- * references to non-aggregated input columns, so no problem.)
- */
- econtext->ecxt_outertuple = firstSlot;
-
- /*
* Check the qual (HAVING clause); if the group does not match, ignore
* it and loop back to try to process another group.
*/
@@ -1568,10 +1687,12 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
int numInputs;
int numSortCols;
int numDistinctCols;
+ bool isOrderedSet = aggref->isordset;
List *sortlist;
HeapTuple aggTuple;
Form_pg_aggregate aggform;
Oid aggtranstype;
+ Oid aggtranstypecoll;
AclResult aclresult;
Oid transfn_oid,
finalfn_oid;
@@ -1580,6 +1701,10 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
Datum textInitVal;
int i;
ListCell *lc;
+ bool is_strict;
+ Oid inputCollations[FUNC_MAX_ARGS];
+ List *argexprs;
+ List *argexprstate;
/* Planner should have assigned aggregate to correct level */
Assert(aggref->agglevelsup == 0);
@@ -1601,31 +1726,18 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
/* Nope, so assign a new PerAgg record */
peraggstate = &peragg[++aggno];
- /* Mark Aggref state node with assigned index in the result array */
- aggrefstate->aggno = aggno;
-
/* Fill in the peraggstate data */
- peraggstate->aggrefstate = aggrefstate;
+ peraggstate->type = T_AggStatePerAggData;
+ peraggstate->aggstate = aggstate;
peraggstate->aggref = aggref;
- numInputs = list_length(aggref->args);
- peraggstate->numInputs = numInputs;
- peraggstate->sortstate = NULL;
+ peraggstate->aggrefstate = aggrefstate;
- /*
- * Get actual datatypes of the inputs. These could be different from
- * the agg's declared input types, when the agg accepts ANY or a
- * polymorphic type.
- */
- numArguments = 0;
- foreach(lc, aggref->args)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ peraggstate->isOrderedSet = isOrderedSet;
- if (!tle->resjunk)
- inputTypes[numArguments++] = exprType((Node *) tle->expr);
- }
- peraggstate->numArguments = numArguments;
+ /* Mark Aggref state node with assigned index in the result array */
+ aggrefstate->aggno = aggno;
+ /* Fetch the pg_aggregate row */
aggTuple = SearchSysCache1(AGGFNOID,
ObjectIdGetDatum(aggref->aggfnoid));
if (!HeapTupleIsValid(aggTuple))
@@ -1633,6 +1745,13 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
aggref->aggfnoid);
aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
+ /*
+ * Check that the definition hasn't somehow changed incompatibly.
+ */
+ if (isOrderedSet != (aggform->aggisordsetfunc)
+ || (aggref->ishypothetical != (aggform->aggordnargs == -2)))
+ elog(ERROR, "incompatible change to aggregate definition");
+
/* Check permission to call aggregate function */
aclresult = pg_proc_aclcheck(aggref->aggfnoid, GetUserId(),
ACL_EXECUTE);
@@ -1644,25 +1763,37 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
- /* Check that aggregate owner has permission to call component fns */
+ /*
+ * Check that aggregate owner has permission to call component fns
+ * In passing, fetch the proisstrict flag for the aggregate proper,
+ * which subs for the transfn's strictness flag in cases where there
+ * is no transfn.
+ */
{
HeapTuple procTuple;
Oid aggOwner;
+ Form_pg_proc procp;
procTuple = SearchSysCache1(PROCOID,
ObjectIdGetDatum(aggref->aggfnoid));
if (!HeapTupleIsValid(procTuple))
elog(ERROR, "cache lookup failed for function %u",
aggref->aggfnoid);
- aggOwner = ((Form_pg_proc) GETSTRUCT(procTuple))->proowner;
+ procp = (Form_pg_proc) GETSTRUCT(procTuple);
+ aggOwner = procp->proowner;
+ is_strict = procp->proisstrict;
ReleaseSysCache(procTuple);
- aclresult = pg_proc_aclcheck(transfn_oid, aggOwner,
- ACL_EXECUTE);
- if (aclresult != ACLCHECK_OK)
+ if (OidIsValid(transfn_oid))
+ {
+ aclresult = pg_proc_aclcheck(transfn_oid, aggOwner,
+ ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK && OidIsValid(transfn_oid))
aclcheck_error(aclresult, ACL_KIND_PROC,
get_func_name(transfn_oid));
- InvokeFunctionExecuteHook(transfn_oid);
+ InvokeFunctionExecuteHook(transfn_oid);
+ }
+
if (OidIsValid(finalfn_oid))
{
aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
@@ -1674,17 +1805,37 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
}
}
+ /*
+ * Get actual datatypes of the inputs. These could be different from
+ * the agg's declared input types, when the agg accepts ANY or a
+ * polymorphic type.
+ */
+
+ peraggstate->numInputs = numInputs = list_length(aggref->args);
+
+ numArguments = get_aggregate_argtypes(aggref,
+ inputTypes,
+ inputCollations);
+
/* resolve actual type of transition state, if polymorphic */
aggtranstype = aggform->aggtranstype;
- if (IsPolymorphicType(aggtranstype))
+ if (OidIsValid(aggtranstype) && IsPolymorphicType(aggtranstype))
{
/* have to fetch the agg's declared input types... */
Oid *declaredArgTypes;
- int agg_nargs;
+ int agg_nargs;
(void) get_func_signature(aggref->aggfnoid,
- &declaredArgTypes, &agg_nargs);
- Assert(agg_nargs == numArguments);
+ &declaredArgTypes,
+ &agg_nargs);
+
+ /*
+ * if variadic "any", might be more actual args than declared
+ * args, but these extra args can't influence the determination
+ * of polymorphic transition or result type.
+ */
+ Assert(agg_nargs <= numArguments);
+
aggtranstype = enforce_generic_type_consistency(inputTypes,
declaredArgTypes,
agg_nargs,
@@ -1693,35 +1844,82 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
pfree(declaredArgTypes);
}
+ aggtranstypecoll = get_typcollation(aggtranstype);
+
/* build expression trees using actual argument & result types */
- build_aggregate_fnexprs(inputTypes,
- numArguments,
- aggref->aggvariadic,
- aggtranstype,
- aggref->aggtype,
- aggref->inputcollid,
- transfn_oid,
- finalfn_oid,
- &transfnexpr,
- &finalfnexpr);
-
- fmgr_info(transfn_oid, &peraggstate->transfn);
- fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
+
+ if (!isOrderedSet)
+ {
+ build_aggregate_fnexprs(inputTypes,
+ numArguments,
+ aggref->aggvariadic,
+ aggtranstype,
+ aggref->aggtype,
+ aggref->inputcollid,
+ transfn_oid,
+ finalfn_oid,
+ &transfnexpr,
+ &finalfnexpr);
+ }
+ else
+ {
+ /*
+ * The transvalue counts as an argument, but not for hypothetical
+ * set funcs.
+ */
+ if (OidIsValid(aggtranstype) && !(aggref->ishypothetical))
+ {
+ if (numArguments == FUNC_MAX_ARGS)
+ elog(ERROR, "too many arguments to ordered set function");
+
+ inputTypes[numArguments++] = aggtranstype;
+ inputCollations[numArguments++] = aggtranstypecoll;
+ }
+
+ build_orderedset_fnexprs(inputTypes,
+ numArguments,
+ aggref->aggvariadic,
+ aggref->aggtype,
+ aggref->inputcollid,
+ inputCollations,
+ finalfn_oid,
+ &finalfnexpr);
+ }
+
+ peraggstate->numArguments = numArguments;
+
+ if (OidIsValid(transfn_oid))
+ {
+ fmgr_info(transfn_oid, &peraggstate->transfn);
+ fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
+
+ is_strict = peraggstate->transfn.fn_strict;
+ }
if (OidIsValid(finalfn_oid))
{
fmgr_info(finalfn_oid, &peraggstate->finalfn);
fmgr_info_set_expr((Node *) finalfnexpr, &peraggstate->finalfn);
+ if (peraggstate->finalfn.fn_strict && isOrderedSet)
+ elog(ERROR, "ordered set finalfns must not be strict");
}
+ if (is_strict)
+ peraggstate->numStrict = (isOrderedSet ? numInputs : numArguments);
+ else
+ peraggstate->numStrict = 0;
+
peraggstate->aggCollation = aggref->inputcollid;
get_typlenbyval(aggref->aggtype,
&peraggstate->resulttypeLen,
&peraggstate->resulttypeByVal);
- get_typlenbyval(aggtranstype,
- &peraggstate->transtypeLen,
- &peraggstate->transtypeByVal);
+ if (OidIsValid(aggtranstype))
+ {
+ get_typlenbyval(aggtranstype,
+ &peraggstate->transtypeLen,
+ &peraggstate->transtypeByVal);
+ }
/*
* initval is potentially null, so don't try to access it as a struct
@@ -1744,7 +1942,9 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
* transValue. This should have been checked at agg definition time,
* but just in case...
*/
- if (peraggstate->transfn.fn_strict && peraggstate->initValueIsNull)
+ if (OidIsValid(peraggstate->transfn_oid)
+ && peraggstate->transfn.fn_strict
+ && peraggstate->initValueIsNull)
{
if (numArguments < 1 ||
!IsBinaryCoercible(inputTypes[0], aggtranstype))
@@ -1754,21 +1954,8 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
aggref->aggfnoid)));
}
- /*
- * Get a tupledesc corresponding to the inputs (including sort
- * expressions) of the agg.
- */
- peraggstate->evaldesc = ExecTypeFromTL(aggref->args, false);
-
- /* Create slot we're going to do argument evaluation in */
- peraggstate->evalslot = ExecInitExtraTupleSlot(estate);
- ExecSetSlotDescriptor(peraggstate->evalslot, peraggstate->evaldesc);
-
- /* Set up projection info for evaluation */
- peraggstate->evalproj = ExecBuildProjectionInfo(aggrefstate->args,
- aggstate->tmpcontext,
- peraggstate->evalslot,
- NULL);
+ argexprs = aggref->args;
+ argexprstate = aggrefstate->args;
/*
* If we're doing either DISTINCT or ORDER BY, then we have a list of
@@ -1777,6 +1964,11 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
*
* Note that by construction, if there is a DISTINCT clause then the
* ORDER BY clause is a prefix of it (see transformDistinctClause).
+ *
+ * If we're doing an ordered set function, though, we want to do the
+ * initialization for DISTINCT since the ordered set finalfn might
+ * want it, and it's much easier to do it here. So set numDistinctCols
+ * and let the later initialization take care of it.
*/
if (aggref->aggdistinct)
{
@@ -1788,11 +1980,86 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
{
sortlist = aggref->aggorder;
numSortCols = list_length(sortlist);
- numDistinctCols = 0;
+ numDistinctCols = isOrderedSet ? numSortCols : 0;
+ }
+
+ /*
+ * If this is an ordered set function, and we have a transtype, then
+ * it represents an extra column to be added to the sorter with a
+ * fixed value. Plus, if aggtranssortop is valid, we have to include
+ * a sort entry for the new column.
+ *
+ * I'd probably have done this in the planner if I'd seen any
+ * possible place to put it; if there is one, it's very obscure.
+ */
+
+ if (OidIsValid(aggtranstype) && isOrderedSet)
+ {
+ Oid sortop = aggform->aggtranssortop;
+ Const *node = makeNode(Const);
+ TargetEntry *tle;
+ SortGroupClause *sortcl = NULL;
+
+ node->consttype = aggtranstype;
+ node->consttypmod = -1;
+ node->constcollid = aggtranstypecoll;
+ node->constlen = peraggstate->transtypeLen;
+ node->constvalue = peraggstate->initValue;
+ node->constisnull = peraggstate->initValueIsNull;
+ node->constbyval = peraggstate->transtypeByVal;
+ node->location = -1;
+
+ tle = makeTargetEntry((Expr *) node,
+ ++numInputs,
+ NULL,
+ true);
+
+ peraggstate->numInputs = numInputs;
+
+ if (OidIsValid(sortop))
+ {
+ Assert(aggref->aggdistinct == NIL);
+
+ sortcl = makeNode(SortGroupClause);
+
+ sortcl->tleSortGroupRef = assignSortGroupRef(tle, argexprs);
+
+ sortcl->sortop = sortop;
+ sortcl->hashable = false;
+ sortcl->eqop = get_equality_op_for_ordering_op(sortop,
+ &sortcl->nulls_first);
+
+ sortlist = lappend(list_copy(sortlist), sortcl);
+ ++numSortCols;
+ ++numDistinctCols;
+ }
+
+ /* shallow-copy the passed-in lists, which we must not scribble on. */
+
+ argexprs = lappend(list_copy(argexprs), (Node *) tle);
+ argexprstate = lappend(list_copy(argexprstate),
+ ExecInitExpr((Expr *) tle, (PlanState *) aggstate));
}
peraggstate->numSortCols = numSortCols;
peraggstate->numDistinctCols = numDistinctCols;
+ peraggstate->sortstate = NULL;
+
+ /*
+ * Get a tupledesc corresponding to the inputs (including sort
+ * expressions) of the agg.
+ */
+ peraggstate->evaldesc = ExecTypeFromTL(argexprs, false);
+
+ /* Create slot we're going to do argument evaluation in */
+ peraggstate->evalslot = ExecInitExtraTupleSlot(estate);
+ ExecSetSlotDescriptor(peraggstate->evalslot, peraggstate->evaldesc);
+
+ /* Set up projection info for evaluation */
+ peraggstate->evalproj = ExecBuildProjectionInfo(argexprstate,
+ aggstate->tmpcontext,
+ peraggstate->evalslot,
+ NULL);
if (numSortCols > 0)
{
@@ -1805,11 +2072,12 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
/* If we have only one input, we need its len/byval info. */
if (numInputs == 1)
{
- get_typlenbyval(inputTypes[0],
+ get_typlenbyval(peraggstate->evaldesc->attrs[0]->atttypid,
&peraggstate->inputtypeLen,
&peraggstate->inputtypeByVal);
}
- else if (numDistinctCols > 0)
+
+ if (numDistinctCols > 0 && (numInputs > 1 || isOrderedSet))
{
/* we will need an extra slot to store prior values */
peraggstate->uniqslot = ExecInitExtraTupleSlot(estate);
@@ -1822,50 +2090,47 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
(AttrNumber *) palloc(numSortCols * sizeof(AttrNumber));
peraggstate->sortOperators =
(Oid *) palloc(numSortCols * sizeof(Oid));
+ peraggstate->sortEqOperators =
+ (Oid *) palloc(numSortCols * sizeof(Oid));
peraggstate->sortCollations =
(Oid *) palloc(numSortCols * sizeof(Oid));
peraggstate->sortNullsFirst =
(bool *) palloc(numSortCols * sizeof(bool));
+ if (numDistinctCols > 0)
+ peraggstate->equalfns =
+ (FmgrInfo *) palloc(numDistinctCols * sizeof(FmgrInfo));
+ else
+ peraggstate->equalfns = NULL;
+
i = 0;
foreach(lc, sortlist)
{
SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
TargetEntry *tle = get_sortgroupclause_tle(sortcl,
- aggref->args);
+ argexprs);
/* the parser should have made sure of this */
Assert(OidIsValid(sortcl->sortop));
peraggstate->sortColIdx[i] = tle->resno;
peraggstate->sortOperators[i] = sortcl->sortop;
+ peraggstate->sortEqOperators[i] = sortcl->eqop;
peraggstate->sortCollations[i] = exprCollation((Node *) tle->expr);
peraggstate->sortNullsFirst[i] = sortcl->nulls_first;
- i++;
- }
- Assert(i == numSortCols);
- }
- if (aggref->aggdistinct)
- {
- Assert(numArguments > 0);
-
- /*
- * We need the equal function for each DISTINCT comparison we will
- * make.
- */
- peraggstate->equalfns =
- (FmgrInfo *) palloc(numDistinctCols * sizeof(FmgrInfo));
-
- i = 0;
- foreach(lc, aggref->aggdistinct)
- {
- SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
+ /*
+ * It's OK to get the equalfns here too, since we already
+ * require that sortlist is aggref->aggdistinct for the normal
+ * distinct case, and for ordered set functions using the
+ * (possibly modified copy of) aggref->aggorder is correct
+ */
+ if (peraggstate->equalfns)
+ fmgr_info(get_opcode(sortcl->eqop), &peraggstate->equalfns[i]);
- fmgr_info(get_opcode(sortcl->eqop), &peraggstate->equalfns[i]);
i++;
}
- Assert(i == numDistinctCols);
+ Assert(i == numSortCols);
}
ReleaseSysCache(aggTuple);
@@ -2023,6 +2288,9 @@ ExecReScanAgg(AggState *node)
* If aggcontext isn't NULL, the function also stores at *aggcontext the
* identity of the memory context that aggregate transition values are
* being stored in.
+ *
+ * We do NOT include AGG_CONTEXT_ORDERED as a possible return here, since
+ * that would open a security hole.
*/
int
AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
@@ -2063,3 +2331,118 @@ aggregate_dummy(PG_FUNCTION_ARGS)
fcinfo->flinfo->fn_oid);
return (Datum) 0; /* keep compiler quiet */
}
+
+/* AggSetGetRowCount - Get the number of rows in case of ordered set
+ * functions.
+ */
+int64
+AggSetGetRowCount(FunctionCallInfo fcinfo)
+{
+ if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ {
+ return ((AggStatePerAggData *)fcinfo->context)->number_of_rows;
+ }
+
+ elog(ERROR, "Called AggSetGetRowCount on non ordered set function");
+ return -1;
+}
+
+/* AggSetGetSortInfo - Get the sort state in the case of
+ * ordered set functions.
+ */
+void
+AggSetGetSortInfo(FunctionCallInfo fcinfo,
+ Tuplesortstate **sortstate,
+ TupleDesc *tupdesc,
+ TupleTableSlot **tupslot,
+ Oid *datumtype)
+{
+ if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ {
+ AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+
+ *sortstate = peraggstate->sortstate;
+ if (peraggstate->numInputs == 1)
+ {
+ if (tupdesc)
+ *tupdesc = NULL;
+ if (datumtype)
+ *datumtype = peraggstate->evaldesc->attrs[0]->atttypid;
+ }
+ else
+ {
+ if (tupdesc)
+ *tupdesc = peraggstate->evaldesc;
+ if (datumtype)
+ *datumtype = InvalidOid;
+ }
+
+ if (tupslot)
+ *tupslot = peraggstate->evalslot;
+ }
+ else
+ elog(ERROR, "AggSetSortInfo called on non ordered set function");
+}
+
+int
+AggSetGetDistinctInfo(FunctionCallInfo fcinfo,
+ TupleTableSlot **uniqslot,
+ AttrNumber **sortColIdx,
+ FmgrInfo **equalfns)
+{
+ if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ {
+ AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+
+ if (uniqslot)
+ *uniqslot = peraggstate->uniqslot;
+ if (sortColIdx)
+ *sortColIdx = peraggstate->sortColIdx;
+ if (equalfns)
+ *equalfns = peraggstate->equalfns;
+
+ return peraggstate->numDistinctCols;
+ }
+ else
+ elog(ERROR, "AggSetGetDistinctOperators called on non ordered set function");
+}
+
+int
+AggSetGetSortOperators(FunctionCallInfo fcinfo,
+ AttrNumber **sortColIdx,
+ Oid **sortOperators,
+ Oid **sortEqOperators,
+ Oid **sortCollations,
+ bool **sortNullsFirst)
+{
+ if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ {
+ AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+
+ if (sortColIdx)
+ *sortColIdx = peraggstate->sortColIdx;
+ if (sortOperators)
+ *sortOperators = peraggstate->sortOperators;
+ if (sortEqOperators)
+ *sortEqOperators = peraggstate->sortEqOperators;
+ if (sortCollations)
+ *sortCollations = peraggstate->sortCollations;
+ if (sortNullsFirst)
+ *sortNullsFirst = peraggstate->sortNullsFirst;
+
+ return peraggstate->numSortCols;
+ }
+ else
+ elog(ERROR, "AggSetGetSortOperators called on non ordered set function");
+}
+
+void
+AggSetGetPerTupleContext(FunctionCallInfo fcinfo,
+ MemoryContext *memcontext)
+{
+ if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ {
+ AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+ *memcontext = peraggstate->aggstate->tmpcontext->ecxt_per_tuple_memory;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1733da6..b9446d4 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1139,9 +1139,12 @@ _copyAggref(const Aggref *from)
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(aggorder);
COPY_NODE_FIELD(aggdistinct);
+ COPY_NODE_FIELD(orddirectargs);
COPY_NODE_FIELD(aggfilter);
COPY_SCALAR_FIELD(aggstar);
COPY_SCALAR_FIELD(aggvariadic);
+ COPY_SCALAR_FIELD(isordset);
+ COPY_SCALAR_FIELD(ishypothetical);
COPY_SCALAR_FIELD(agglevelsup);
COPY_LOCATION_FIELD(location);
@@ -2174,6 +2177,7 @@ _copyFuncCall(const FuncCall *from)
COPY_SCALAR_FIELD(agg_star);
COPY_SCALAR_FIELD(agg_distinct);
COPY_SCALAR_FIELD(func_variadic);
+ COPY_SCALAR_FIELD(has_within_group);
COPY_NODE_FIELD(over);
COPY_LOCATION_FIELD(location);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 7b29812..05c239c 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -196,9 +196,12 @@ _equalAggref(const Aggref *a, const Aggref *b)
COMPARE_NODE_FIELD(args);
COMPARE_NODE_FIELD(aggorder);
COMPARE_NODE_FIELD(aggdistinct);
+ COMPARE_NODE_FIELD(orddirectargs);
COMPARE_NODE_FIELD(aggfilter);
COMPARE_SCALAR_FIELD(aggstar);
COMPARE_SCALAR_FIELD(aggvariadic);
+ COMPARE_SCALAR_FIELD(isordset);
+ COMPARE_SCALAR_FIELD(ishypothetical);
COMPARE_SCALAR_FIELD(agglevelsup);
COMPARE_LOCATION_FIELD(location);
@@ -2015,6 +2018,7 @@ _equalFuncCall(const FuncCall *a, const FuncCall *b)
COMPARE_SCALAR_FIELD(agg_star);
COMPARE_SCALAR_FIELD(agg_distinct);
COMPARE_SCALAR_FIELD(func_variadic);
+ COMPARE_SCALAR_FIELD(has_within_group);
COMPARE_NODE_FIELD(over);
COMPARE_LOCATION_FIELD(location);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 4a7e793..4529dca 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -558,6 +558,7 @@ makeFuncCall(List *name, List *args, int location)
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->has_within_group = FALSE;
n->over = NULL;
return n;
}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 908f397..e84b371 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1631,6 +1631,11 @@ expression_tree_walker(Node *node,
if (expression_tree_walker((Node *) expr->aggdistinct,
walker, context))
return true;
+
+ if (expression_tree_walker((Node *) expr->orddirectargs,
+ walker, context))
+ return true;
+
if (walker((Node *) expr->aggfilter, context))
return true;
}
@@ -2155,7 +2160,9 @@ expression_tree_mutator(Node *node,
MUTATE(newnode->args, aggref->args, List *);
MUTATE(newnode->aggorder, aggref->aggorder, List *);
MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
+ MUTATE(newnode->orddirectargs, aggref->orddirectargs, List *);
MUTATE(newnode->aggfilter, aggref->aggfilter, Expr *);
+
return (Node *) newnode;
}
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 817b149..8f6e293 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -960,9 +960,12 @@ _outAggref(StringInfo str, const Aggref *node)
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(aggorder);
WRITE_NODE_FIELD(aggdistinct);
+ WRITE_NODE_FIELD(orddirectargs);
WRITE_NODE_FIELD(aggfilter);
WRITE_BOOL_FIELD(aggstar);
WRITE_BOOL_FIELD(aggvariadic);
+ WRITE_BOOL_FIELD(isordset);
+ WRITE_BOOL_FIELD(ishypothetical);
WRITE_UINT_FIELD(agglevelsup);
WRITE_LOCATION_FIELD(location);
}
@@ -2090,6 +2093,7 @@ _outFuncCall(StringInfo str, const FuncCall *node)
WRITE_BOOL_FIELD(agg_star);
WRITE_BOOL_FIELD(agg_distinct);
WRITE_BOOL_FIELD(func_variadic);
+ WRITE_BOOL_FIELD(has_within_group);
WRITE_NODE_FIELD(over);
WRITE_LOCATION_FIELD(location);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index d325bb3..950645d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -495,9 +495,12 @@ _readAggref(void)
READ_NODE_FIELD(args);
READ_NODE_FIELD(aggorder);
READ_NODE_FIELD(aggdistinct);
+ READ_NODE_FIELD(orddirectargs);
READ_NODE_FIELD(aggfilter);
READ_BOOL_FIELD(aggstar);
READ_BOOL_FIELD(aggvariadic);
+ READ_BOOL_FIELD(isordset);
+ READ_BOOL_FIELD(ishypothetical);
READ_UINT_FIELD(agglevelsup);
READ_LOCATION_FIELD(location);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index add29f5..6c24582 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -39,6 +39,7 @@
#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
+#include "parser/parse_agg.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -464,7 +465,6 @@ count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
QualCost argcosts;
Oid *inputTypes;
int numArguments;
- ListCell *l;
Assert(aggref->agglevelsup == 0);
@@ -486,7 +486,8 @@ count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
costs->numOrderedAggs++;
/* add component function execution costs to appropriate totals */
- costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost;
+ if (OidIsValid(aggtransfn))
+ costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost;
if (OidIsValid(aggfinalfn))
costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost;
@@ -504,72 +505,91 @@ count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
costs->transCost.startup += argcosts.startup;
costs->transCost.per_tuple += argcosts.per_tuple;
- /* extract argument types (ignoring any ORDER BY expressions) */
- inputTypes = (Oid *) palloc(sizeof(Oid) * list_length(aggref->args));
- numArguments = 0;
- foreach(l, aggref->args)
+ /*
+ * If we're doing a sorted agg, we can punt the entire
+ * determination of transition element size since we're not
+ * going to be using it to determine hashtable limits. This
+ * simplifies the code for hypothetical set functions.
+ */
+
+ if (aggref->aggorder == NIL && aggref->aggdistinct == NIL)
{
- TargetEntry *tle = (TargetEntry *) lfirst(l);
+ Assert(!aggref->isordset);
- if (!tle->resjunk)
- inputTypes[numArguments++] = exprType((Node *) tle->expr);
- }
+ /* extract argument types (ignoring any ORDER BY expressions) */
+ inputTypes = (Oid *) palloc(sizeof(Oid) * FUNC_MAX_ARGS);
- /* resolve actual type of transition state, if polymorphic */
- if (IsPolymorphicType(aggtranstype))
- {
- /* have to fetch the agg's declared input types... */
- Oid *declaredArgTypes;
- int agg_nargs;
-
- (void) get_func_signature(aggref->aggfnoid,
- &declaredArgTypes, &agg_nargs);
- Assert(agg_nargs == numArguments);
- aggtranstype = enforce_generic_type_consistency(inputTypes,
- declaredArgTypes,
- agg_nargs,
- aggtranstype,
- false);
- pfree(declaredArgTypes);
- }
+ numArguments = get_aggregate_argtypes(aggref, inputTypes, NULL);
- /*
- * If the transition type is pass-by-value then it doesn't add
- * anything to the required size of the hashtable. If it is
- * pass-by-reference then we have to add the estimated size of the
- * value itself, plus palloc overhead.
- */
- if (!get_typbyval(aggtranstype))
- {
- int32 aggtranstypmod;
- int32 avgwidth;
+ /* resolve actual type of transition state, if polymorphic */
+ if (OidIsValid(aggtranstype) && IsPolymorphicType(aggtranstype))
+ {
+ /* have to fetch the agg's declared input types... */
+ Oid *declaredArgTypes;
+ int agg_nargs;
+
+ (void) get_func_signature(aggref->aggfnoid,
+ &declaredArgTypes, &agg_nargs);
+
+ /*
+ * if variadic "any", might be more actual args than declared
+ * args, but these extra args can't influence the determination
+ * of polymorphic transition or result type.
+ */
+ Assert(agg_nargs <= numArguments);
+
+ aggtranstype = enforce_generic_type_consistency(inputTypes,
+ declaredArgTypes,
+ agg_nargs,
+ aggtranstype,
+ false);
+ pfree(declaredArgTypes);
+ }
/*
- * If transition state is of same type as first input, assume it's
- * the same typmod (same width) as well. This works for cases
- * like MAX/MIN and is probably somewhat reasonable otherwise.
+ * If the transition type is pass-by-value then it doesn't add
+ * anything to the required size of the hashtable. If it is
+ * pass-by-reference then we have to add the estimated size of the
+ * value itself, plus palloc overhead.
*/
- if (numArguments > 0 && aggtranstype == inputTypes[0])
- aggtranstypmod = exprTypmod((Node *) linitial(aggref->args));
- else
- aggtranstypmod = -1;
+ if (OidIsValid(aggtranstype) && !get_typbyval(aggtranstype))
+ {
+ int32 aggtranstypmod;
+ int32 avgwidth;
+
+ /*
+ * If transition state is of same type as first input, assume it's
+ * the same typmod (same width) as well. This works for cases
+ * like MAX/MIN and is probably somewhat reasonable otherwise.
+ */
+ if (numArguments > 0 && aggtranstype == inputTypes[0])
+ aggtranstypmod = exprTypmod((Node *) linitial(aggref->args));
+ else
+ aggtranstypmod = -1;
- avgwidth = get_typavgwidth(aggtranstype, aggtranstypmod);
- avgwidth = MAXALIGN(avgwidth);
+ avgwidth = get_typavgwidth(aggtranstype, aggtranstypmod);
+ avgwidth = MAXALIGN(avgwidth);
- costs->transitionSpace += avgwidth + 2 * sizeof(void *);
+ costs->transitionSpace += avgwidth + 2 * sizeof(void *);
+ }
+ else if (aggtranstype == INTERNALOID)
+ {
+ /*
+ * INTERNAL transition type is a special case: although INTERNAL
+ * is pass-by-value, it's almost certainly being used as a pointer
+ * to some large data structure. We assume usage of
+ * ALLOCSET_DEFAULT_INITSIZE, which is a good guess if the data is
+ * being kept in a private memory context, as is done by
+ * array_agg() for instance.
+ */
+ costs->transitionSpace += ALLOCSET_DEFAULT_INITSIZE;
+ }
+
+ pfree(inputTypes);
}
- else if (aggtranstype == INTERNALOID)
+ else
{
- /*
- * INTERNAL transition type is a special case: although INTERNAL
- * is pass-by-value, it's almost certainly being used as a pointer
- * to some large data structure. We assume usage of
- * ALLOCSET_DEFAULT_INITSIZE, which is a good guess if the data is
- * being kept in a private memory context, as is done by
- * array_agg() for instance.
- */
- costs->transitionSpace += ALLOCSET_DEFAULT_INITSIZE;
+ costs->transitionSpace = work_mem; /* just in case */
}
/*
@@ -3897,7 +3917,7 @@ recheck_cast_function_args(List *args, Oid result_type, HeapTuple func_tuple)
elog(ERROR, "function's resolved result type changed during planning");
/* perform any necessary typecasting of arguments */
- make_fn_arguments(NULL, args, actual_arg_types, declared_arg_types);
+ make_fn_arguments(NULL, args, NULL, actual_arg_types, declared_arg_types, false);
}
/*
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index a9d1fec..ae168ad 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -951,7 +951,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
&qry->targetList,
EXPR_KIND_ORDER_BY,
true /* fix unknowns */ ,
- false /* allow SQL92 rules */ );
+ false /* allow SQL92 rules */,
+ false /* don't add duplicates */);
qry->groupClause = transformGroupClause(pstate,
stmt->groupClause,
@@ -1211,7 +1212,8 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
&qry->targetList,
EXPR_KIND_ORDER_BY,
true /* fix unknowns */ ,
- false /* allow SQL92 rules */ );
+ false /* allow SQL92 rules */,
+ false /* don't add duplicates */ );
qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
EXPR_KIND_OFFSET, "OFFSET");
@@ -1435,7 +1437,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
&qry->targetList,
EXPR_KIND_ORDER_BY,
false /* no unknowns expected */ ,
- false /* allow SQL92 rules */ );
+ false /* allow SQL92 rules */ ,
+ false /* don't add duplicates */ );
/* restore namespace, remove jrte from rtable */
pstate->p_namespace = sv_namespace;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 11f6291..7cb3591 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -495,6 +495,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
%type <node> filter_clause
+%type <list> within_group_clause
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -597,7 +598,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
VERBOSE VERSION_P VIEW VOLATILE
- WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
+ WHEN WHERE WITHIN WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
XMLPI XMLROOT XMLSERIALIZE
@@ -3704,7 +3705,7 @@ AlterExtensionContentsStmt:
n->action = $4;
n->objtype = OBJECT_AGGREGATE;
n->objname = $6;
- n->objargs = extractArgTypes($7);
+ n->objargs = extractArgTypes(linitial($7));
$$ = (Node *)n;
}
| ALTER EXTENSION name add_drop CAST '(' Typename AS Typename ')'
@@ -5283,7 +5284,7 @@ CommentStmt:
CommentStmt *n = makeNode(CommentStmt);
n->objtype = OBJECT_AGGREGATE;
n->objname = $4;
- n->objargs = extractArgTypes($5);
+ n->objargs = extractArgTypes(linitial($5));
n->comment = $7;
$$ = (Node *) n;
}
@@ -5449,7 +5450,7 @@ SecLabelStmt:
n->provider = $3;
n->objtype = OBJECT_AGGREGATE;
n->objname = $6;
- n->objargs = extractArgTypes($7);
+ n->objargs = extractArgTypes(linitial($7));
n->label = $9;
$$ = (Node *) n;
}
@@ -6449,9 +6450,53 @@ aggr_arg: func_arg
}
;
-/* Zero-argument aggregates are named with * for consistency with COUNT(*) */
-aggr_args: '(' aggr_args_list ')' { $$ = $2; }
- | '(' '*' ')' { $$ = NIL; }
+/*
+ * Aggregate args (for create aggregate, etc.) are treated as follows:
+ *
+ * (*) - no args
+ * (func_arg,func_arg,...) - normal agg with args
+ * () within group (func_arg,...) - ordered set func with no direct args
+ * (func_arg,...) within group (func_arg,...) - ordered set func with args
+ * (func_arg,...) within group (*) - ordered set func variadic special case
+ *
+ * This doesn't correspond to anything in the spec because the spec doesn't
+ * have any DDL to create or modify ordered set functions, so we're winging
+ * it here.
+ *
+ * Almost everything we do with an ordered set function treats its arguments
+ * as though they were a single list, with the direct and grouped arg types
+ * concatenated. So for simplicity, we construct a single list here.
+ *
+ * But we still need to know when creating an agg (but not for referring to it
+ * later) where the division between direct and ordered args is; so this
+ * production returns a pair (arglist,num) where num is the number of direct
+ * args, or -1 if no within group clause was used. Most users of aggr_args,
+ * other than CREATE AGGREGATE, therefore only need to pay attention to
+ * linitial($n).
+ */
+
+aggr_args: '(' '*' ')'
+ {
+ $$ = list_make2(NIL, makeInteger(-1));
+ }
+ | '(' aggr_args_list ')'
+ {
+ $$ = list_make2($2, makeInteger(-1));
+ }
+ | '(' ')' WITHIN GROUP_P '(' aggr_args_list ')'
+ {
+ $$ = list_make2($6, makeInteger(0));
+ }
+ | '(' aggr_args_list ')' WITHIN GROUP_P '(' aggr_args_list ')'
+ {
+ int n = list_length($2);
+ $$ = list_make2(list_concat($2,$7), makeInteger(n));
+ }
+ | '(' aggr_args_list ')' WITHIN GROUP_P '(' '*' ')'
+ {
+ int n = list_length($2);
+ $$ = list_make2($2, makeInteger(n));
+ }
;
aggr_args_list:
@@ -6657,7 +6702,7 @@ RemoveAggrStmt:
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_AGGREGATE;
n->objects = list_make1($3);
- n->arguments = list_make1(extractArgTypes($4));
+ n->arguments = list_make1(extractArgTypes(linitial($4)));
n->behavior = $5;
n->missing_ok = false;
n->concurrent = false;
@@ -6668,7 +6713,7 @@ RemoveAggrStmt:
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_AGGREGATE;
n->objects = list_make1($5);
- n->arguments = list_make1(extractArgTypes($6));
+ n->arguments = list_make1(extractArgTypes(linitial($6)));
n->behavior = $7;
n->missing_ok = true;
n->concurrent = false;
@@ -6884,7 +6929,7 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
RenameStmt *n = makeNode(RenameStmt);
n->renameType = OBJECT_AGGREGATE;
n->object = $3;
- n->objarg = extractArgTypes($4);
+ n->objarg = extractArgTypes(linitial($4));
n->newname = $7;
n->missing_ok = false;
$$ = (Node *)n;
@@ -7358,7 +7403,7 @@ AlterObjectSchemaStmt:
AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
n->objectType = OBJECT_AGGREGATE;
n->object = $3;
- n->objarg = extractArgTypes($4);
+ n->objarg = extractArgTypes(linitial($4));
n->newschema = $7;
n->missing_ok = false;
$$ = (Node *)n;
@@ -7587,7 +7632,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_AGGREGATE;
n->object = $3;
- n->objarg = extractArgTypes($4);
+ n->objarg = extractArgTypes(linitial($4));
n->newowner = $7;
$$ = (Node *)n;
}
@@ -9475,6 +9520,11 @@ sortby: a_expr USING qual_all_Op opt_nulls_order
;
+within_group_clause:
+ WITHIN GROUP_P '(' sort_clause ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
select_limit:
limit_clause offset_clause { $$ = list_make2($2, $1); }
| offset_clause limit_clause { $$ = list_make2($1, $2); }
@@ -11180,12 +11230,35 @@ func_application: func_name '(' ')'
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
-func_expr: func_application filter_clause over_clause
+func_expr: func_application within_group_clause filter_clause over_clause
{
- FuncCall *n = (FuncCall*)$1;
- n->agg_filter = $2;
- n->over = $3;
- $$ = (Node*)n;
+ FuncCall *n = (FuncCall *) $1;
+ /*
+ * the order clause for WITHIN GROUP and the one
+ * for aggregate ORDER BY share a field, so we
+ * have to check here that at most one is present.
+ * We check for DISTINCT here to give a better
+ * error position. Other consistency checks are
+ * deferred to parse_func.c or parse_agg.c
+ */
+ if ($2 != NIL)
+ {
+ if (n->agg_order != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot have multiple ORDER BY clauses with WITHIN GROUP"),
+ parser_errposition(@2)));
+ if (n->agg_distinct)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot have DISTINCT and WITHIN GROUP together"),
+ parser_errposition(@2)));
+ n->agg_order = $2;
+ n->has_within_group = TRUE;
+ }
+ n->agg_filter = $3;
+ n->over = $4;
+ $$ = (Node *) n;
}
| func_expr_common_subexpr
{ $$ = $1; }
@@ -12744,6 +12817,7 @@ unreserved_keyword:
| VIEW
| VOLATILE
| WHITESPACE_P
+ | WITHIN
| WITHOUT
| WORK
| WRAPPER
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 98cb58a..23954a0 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -44,7 +44,9 @@ typedef struct
int sublevels_up;
} check_ungrouped_columns_context;
-static int check_agg_arguments(ParseState *pstate, List *args, Expr *filter);
+static int check_agg_arguments(ParseState *pstate,
+ List *args,
+ List *agg_ordset, Expr *filter);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
@@ -75,7 +77,8 @@ static bool check_ungrouped_columns_walker(Node *node,
*/
void
transformAggregateCall(ParseState *pstate, Aggref *agg,
- List *args, List *aggorder, bool agg_distinct)
+ List *args, List *aggorder,
+ bool agg_distinct, bool agg_within_group)
{
List *tlist;
List *torder;
@@ -93,12 +96,24 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
*/
tlist = NIL;
attno = 1;
- foreach(lc, args)
+
+ if (agg_within_group)
{
- Expr *arg = (Expr *) lfirst(lc);
- TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false);
+ agg->isordset = TRUE;
+ agg->orddirectargs = args;
+ }
+ else
+ {
+ foreach(lc, args)
+ {
+ Expr *arg = (Expr *) lfirst(lc);
+ TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false);
- tlist = lappend(tlist, tle);
+ tlist = lappend(tlist, tle);
+ }
+
+ agg->isordset = FALSE;
+ agg->orddirectargs = NIL;
}
/*
@@ -109,6 +124,11 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
*
* We need to mess with p_next_resno since it will be used to number any
* new targetlist entries.
+ *
+ * If and only if we're doing a WITHIN GROUP list, we preserve any
+ * duplicate expressions in the sort clause. This is needed because the
+ * sort clause of WITHIN GROUP is really an argument list, and we must
+ * keep the number and content of entries matching the specified input.
*/
save_next_resno = pstate->p_next_resno;
pstate->p_next_resno = attno;
@@ -118,7 +138,8 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
&tlist,
EXPR_KIND_ORDER_BY,
true /* fix unknowns */ ,
- true /* force SQL99 rules */ );
+ true /* force SQL99 rules */ ,
+ agg_within_group /* keep duplicates? */ );
/*
* If we have DISTINCT, transform that to produce a distinctList.
@@ -160,7 +181,8 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
- min_varlevel = check_agg_arguments(pstate, agg->args, agg->aggfilter);
+ min_varlevel = check_agg_arguments(pstate,
+ agg->args, agg->orddirectargs, agg->aggfilter);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
@@ -312,7 +334,7 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
* which we can't know until we finish scanning the arguments.
*/
static int
-check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
+check_agg_arguments(ParseState *pstate, List *args, List *agg_ordset, Expr *filter)
{
int agglevel;
check_agg_arguments_context context;
@@ -330,6 +352,9 @@ check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
check_agg_arguments_walker,
(void *) &context);
+ (void) expression_tree_walker((Node *) agg_ordset, check_agg_arguments_walker,
+ (void *) &context);
+
/*
* If we found no vars nor aggs at all, it's a level-zero aggregate;
* otherwise, its level is the minimum of vars or aggs.
@@ -353,8 +378,8 @@ check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("aggregate function calls cannot be nested"),
parser_errposition(pstate,
- locate_agg_of_level((Node *) args,
- agglevel))));
+ locate_agg_of_level((Node *) args,
+ agglevel))));
return agglevel;
}
@@ -823,8 +848,16 @@ check_ungrouped_columns_walker(Node *node,
* We do need to look at aggregates of lower levels, however.
*/
if (IsA(node, Aggref) &&
- (int) ((Aggref *) node)->agglevelsup >= context->sublevels_up)
+ (int) ((Aggref *) node)->agglevelsup > context->sublevels_up)
+ {
return false;
+ }
+ else if (IsA(node, Aggref) &&
+ (int) ((Aggref *) node)->agglevelsup == context->sublevels_up)
+ {
+ return check_ungrouped_columns_walker((Node*)(((Aggref *)node)->orddirectargs),
+ context);
+ }
/*
* If we have any GROUP BY items that are not simple Vars, check to see if
@@ -1042,3 +1075,98 @@ build_aggregate_fnexprs(Oid *agg_input_types,
agg_input_collation,
COERCE_EXPLICIT_CALL);
}
+
+void
+build_orderedset_fnexprs(Oid *agg_input_types,
+ int agg_num_inputs,
+ bool agg_variadic,
+ Oid agg_result_type,
+ Oid agg_input_collation,
+ Oid *agg_input_collation_array,
+ Oid finalfn_oid,
+ Expr **finalfnexpr)
+{
+ FuncExpr *fexpr;
+ Param *argp;
+ List *args = NIL;
+ int i = 0;
+
+ /*
+ * Build arg list to use in the finalfn FuncExpr node. We really only care
+ * that finalfn can discover the actual argument types at runtime using
+ * get_fn_expr_argtype(), so it's okay to use Param nodes that don't
+ * correspond to any real Param.
+ */
+ for (i = 0; i < agg_num_inputs; i++)
+ {
+ argp = makeNode(Param);
+ argp->paramkind = PARAM_EXEC;
+ argp->paramid = -1;
+ argp->paramtype = agg_input_types[i];
+ argp->paramtypmod = -1;
+ argp->paramcollid = agg_input_collation_array[i];
+ argp->location = -1;
+
+ args = lappend(args, argp);
+ }
+
+ fexpr = makeFuncExpr(finalfn_oid,
+ agg_result_type,
+ args,
+ InvalidOid,
+ agg_input_collation,
+ COERCE_EXPLICIT_CALL);
+ fexpr->funcvariadic = agg_variadic;
+ *finalfnexpr = (Expr *) fexpr;
+}
+
+int get_aggregate_argtypes(Aggref *aggref, Oid *inputTypes, Oid *inputCollations)
+{
+ int numArguments = 0;
+ ListCell *lc;
+
+ if (!(aggref->isordset))
+ {
+ foreach(lc, aggref->args)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+ if (!tle->resjunk)
+ {
+ inputTypes[numArguments] = exprType((Node *) tle->expr);
+ if (inputCollations != NULL)
+ inputCollations[numArguments] = exprCollation((Node *) tle->expr);
+ ++numArguments;
+ }
+ }
+ }
+ else
+ {
+ foreach(lc, aggref->orddirectargs)
+ {
+ Node *expr_orddirectargs = lfirst(lc);
+
+ inputTypes[numArguments] = exprType(expr_orddirectargs);
+ if (inputCollations != NULL)
+ inputCollations[numArguments] = exprCollation(expr_orddirectargs);
+
+ ++numArguments;
+ }
+
+ if (!(aggref->ishypothetical))
+ {
+ foreach(lc, aggref->args)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+ inputTypes[numArguments] = exprType((Node *) tle->expr);
+ if (inputCollations != NULL)
+ inputCollations[numArguments] = exprCollation((Node *) tle->expr);
+
+ ++numArguments;
+ }
+ }
+ }
+
+ return numArguments;
+}
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 7a1261d..c445413 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -71,7 +71,8 @@ static void checkExprIsVarFree(ParseState *pstate, Node *n,
static TargetEntry *findTargetlistEntrySQL92(ParseState *pstate, Node *node,
List **tlist, ParseExprKind exprKind);
static TargetEntry *findTargetlistEntrySQL99(ParseState *pstate, Node *node,
- List **tlist, ParseExprKind exprKind);
+ List **tlist, ParseExprKind exprKind,
+ bool keepDuplicates);
static int get_matching_location(int sortgroupref,
List *sortgrouprefs, List *exprs);
static List *addTargetToSortList(ParseState *pstate, TargetEntry *tle,
@@ -1477,7 +1478,7 @@ findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
/*
* Otherwise, we have an expression, so process it per SQL99 rules.
*/
- return findTargetlistEntrySQL99(pstate, node, tlist, exprKind);
+ return findTargetlistEntrySQL99(pstate, node, tlist, exprKind, false);
}
/*
@@ -1492,10 +1493,11 @@ findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
* node the ORDER BY, GROUP BY, etc expression to be matched
* tlist the target list (passed by reference so we can append to it)
* exprKind identifies clause type being processed
+ * keepDuplicates if true, don't try and match to any existing entry
*/
static TargetEntry *
findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
- ParseExprKind exprKind)
+ ParseExprKind exprKind, bool keepDuplicates)
{
TargetEntry *target_result;
ListCell *tl;
@@ -1510,24 +1512,27 @@ findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
*/
expr = transformExpr(pstate, node, exprKind);
- foreach(tl, *tlist)
+ if (!keepDuplicates)
{
- TargetEntry *tle = (TargetEntry *) lfirst(tl);
- Node *texpr;
+ foreach(tl, *tlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(tl);
+ Node *texpr;
- /*
- * Ignore any implicit cast on the existing tlist expression.
- *
- * This essentially allows the ORDER/GROUP/etc item to adopt the same
- * datatype previously selected for a textually-equivalent tlist item.
- * There can't be any implicit cast at top level in an ordinary SELECT
- * tlist at this stage, but the case does arise with ORDER BY in an
- * aggregate function.
- */
- texpr = strip_implicit_coercions((Node *) tle->expr);
+ /*
+ * Ignore any implicit cast on the existing tlist expression.
+ *
+ * This essentially allows the ORDER/GROUP/etc item to adopt the same
+ * datatype previously selected for a textually-equivalent tlist item.
+ * There can't be any implicit cast at top level in an ordinary SELECT
+ * tlist at this stage, but the case does arise with ORDER BY in an
+ * aggregate function.
+ */
+ texpr = strip_implicit_coercions((Node *) tle->expr);
- if (equal(expr, texpr))
- return tle;
+ if (equal(expr, texpr))
+ return tle;
+ }
}
/*
@@ -1569,7 +1574,7 @@ transformGroupClause(ParseState *pstate, List *grouplist,
if (useSQL99)
tle = findTargetlistEntrySQL99(pstate, gexpr,
- targetlist, exprKind);
+ targetlist, exprKind, false);
else
tle = findTargetlistEntrySQL92(pstate, gexpr,
targetlist, exprKind);
@@ -1636,11 +1641,14 @@ transformSortClause(ParseState *pstate,
List **targetlist,
ParseExprKind exprKind,
bool resolveUnknown,
- bool useSQL99)
+ bool useSQL99,
+ bool keepDuplicates)
{
List *sortlist = NIL;
ListCell *olitem;
+ Assert(useSQL99 || !keepDuplicates);
+
foreach(olitem, orderlist)
{
SortBy *sortby = (SortBy *) lfirst(olitem);
@@ -1648,7 +1656,7 @@ transformSortClause(ParseState *pstate,
if (useSQL99)
tle = findTargetlistEntrySQL99(pstate, sortby->node,
- targetlist, exprKind);
+ targetlist, exprKind, keepDuplicates);
else
tle = findTargetlistEntrySQL92(pstate, sortby->node,
targetlist, exprKind);
@@ -1718,7 +1726,8 @@ transformWindowDefinitions(ParseState *pstate,
targetlist,
EXPR_KIND_WINDOW_ORDER,
true /* fix unknowns */ ,
- true /* force SQL99 rules */ );
+ true /* force SQL99 rules */,
+ false /* don't add duplicates */);
partitionClause = transformGroupClause(pstate,
windef->partitionClause,
targetlist,
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index fe57c59..f3499fc 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -73,7 +73,9 @@ typedef struct
static bool assign_query_collations_walker(Node *node, ParseState *pstate);
static bool assign_collations_walker(Node *node,
assign_collations_context *context);
-
+static void assign_aggregate_collations(Aggref *aggref,
+ assign_collations_context *context,
+ assign_collations_context *loccontext);
/*
* assign_query_collations()
@@ -564,44 +566,16 @@ assign_collations_walker(Node *node, assign_collations_context *context)
case T_Aggref:
{
/*
- * Aggref is a special case because expressions
- * used only for ordering shouldn't be taken to
- * conflict with each other or with regular args.
- * So we apply assign_expr_collations() to them
- * rather than passing down our loccontext.
- *
- * Note that we recurse to each TargetEntry, not
- * directly to its contained expression, so that
- * the case above for T_TargetEntry will apply
- * appropriate checks to agg ORDER BY items.
- *
- * Likewise, we assign collations for the (bool)
- * expression in aggfilter, independently of any
- * other args.
- *
- * We need not recurse into the aggorder or
- * aggdistinct lists, because those contain only
- * SortGroupClause nodes which we need not
- * process.
+ * Aggref is special enough that we give it its own
+ * function. The FILTER clause is independent of the
+ * rest of the aggregate, however.
*/
Aggref *aggref = (Aggref *) node;
- ListCell *lc;
- foreach(lc, aggref->args)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(lc);
-
- Assert(IsA(tle, TargetEntry));
- if (tle->resjunk)
- assign_expr_collations(context->pstate,
- (Node *) tle);
- else
- (void) assign_collations_walker((Node *) tle,
- &loccontext);
- }
+ assign_aggregate_collations(aggref, context, &loccontext);
assign_expr_collations(context->pstate,
- (Node *) aggref->aggfilter);
+ (Node *) aggref->aggfilter);
}
break;
case T_WindowFunc:
@@ -802,3 +776,159 @@ assign_collations_walker(Node *node, assign_collations_context *context)
return false;
}
+
+
+/*
+ * Aggref is a special case because expressions used only for ordering
+ * shouldn't be taken to conflict with each other or with regular args. So we
+ * apply assign_expr_collations() to them rather than passing down our
+ * loccontext.
+ *
+ * Note that we recurse to each TargetEntry, not directly to its contained
+ * expression, so that the case above for T_TargetEntry will apply appropriate
+ * checks to agg ORDER BY items.
+ *
+ * We need not recurse into the aggorder or aggdistinct lists, because those
+ * contain only SortGroupClause nodes which we need not process.
+ *
+ * For ordered set functions, it's unfortunately unclear how best to proceed.
+ * The spec-defined inverse distribution functions have only one sort column
+ * and don't allow collatable types, but this is clearly unsatisfactory in the
+ * general case. Compromise by taking the sort column as part of the collation
+ * determination if, and only if, there is only one such column, and force the
+ * final choice of input collation down into the sort column if need be; but
+ * don't error out unless actually necessary (leaving it up to the function to
+ * handle the issue at runtime). This ugly wart is justified by the fact that
+ * there seems to be no other good way to get a result collation for
+ * percentile_* applied to a collatable type.
+ *
+ * But hypothetical set functions are special; they must have
+ * pairwise-assigned collations for each matching pair of args, and again we
+ * need to force the final choice of collation down into the sort column to
+ * ensure that the sort happens on the chosen collation. If there are any
+ * additional args (not allowed in the spec, but a user-defined function might
+ * have some), those contribute to the result collation in the normal way.
+ * (The hypothetical paired args never contribute to the result collation at
+ * all.)
+ */
+
+static Expr *
+relabel_expr_collation(Expr *expr, Oid newcollation)
+{
+ RelabelType *node = makeNode(RelabelType);
+ node->arg = expr;
+ node->resulttype = exprType((Node *)expr);
+ node->resulttypmod = exprTypmod((Node *)expr);
+ node->resultcollid = newcollation;
+ node->relabelformat = COERCE_IMPLICIT_CAST;
+ node->location = exprLocation((Node *)expr);
+ return (Expr *) node;
+}
+
+static void
+assign_aggregate_collations(Aggref *aggref,
+ assign_collations_context *context,
+ assign_collations_context *loccontext)
+{
+ ListCell *lc;
+
+ if (aggref->ishypothetical)
+ {
+ /*-
+ * Hypothetical set function, i.e.
+ * func(..., a,b,c,...) within group (p,q,r,...)
+ *
+ * Any initial set of direct args (before "a") contributes to the
+ * result collation in the usual way for function args. But none of
+ * a,b,c... or p,q,r... contribute at all; instead, they must be
+ * paired up (as though UNIONed) and the sorted col's collation forced
+ * to the chosen value (so that we sort it correctly).
+ */
+ int initial_args = list_length(aggref->orddirectargs) - list_length(aggref->args);
+ ListCell *h_arg = list_head(aggref->orddirectargs);
+ ListCell *s_arg = list_head(aggref->args);
+
+ Assert(initial_args >= 0);
+
+ while (initial_args-- > 0)
+ {
+ (void) assign_collations_walker((Node *) lfirst(h_arg), loccontext);
+ h_arg = lnext(h_arg);
+ }
+
+ for_each_cell(h_arg,h_arg)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(s_arg);
+ Oid coll = select_common_collation(context->pstate,
+ list_make2(lfirst(h_arg),lfirst(s_arg)),
+ false);
+
+ /*
+ * we can only get InvalidOid here if the type is not collatable,
+ * so no need to try and relabel in that case.
+ */
+
+ if (OidIsValid(coll)
+ && coll != exprCollation((Node *)(tle->expr)))
+ {
+ tle->expr = relabel_expr_collation(tle->expr, coll);
+ }
+
+ s_arg = lnext(s_arg);
+ }
+ }
+ else if (aggref->isordset && list_length(aggref->args) == 1)
+ {
+ /*
+ * Ordered set func with one sorted arg
+ */
+ TargetEntry *tle = (TargetEntry *) linitial(aggref->args);
+
+ /* do the TLE first so that it won't error out on conflicts */
+
+ (void) assign_collations_walker((Node *) tle,
+ loccontext);
+
+ (void) assign_collations_walker((Node *) aggref->orddirectargs,
+ loccontext);
+
+ /*
+ * If the sort col is a collatable type, and we chose a collation,
+ * and it's not the one the sort col already has, then force the
+ * sort col's collation (which can't have been explicit) to the
+ * chosen one. Otherwise leave it alone.
+ */
+ if (type_is_collatable(exprType((Node *)(tle->expr)))
+ && (loccontext->strength == COLLATE_IMPLICIT
+ || loccontext->strength == COLLATE_EXPLICIT)
+ && exprCollation((Node *)(tle->expr)) != loccontext->collation)
+ {
+ tle->expr = relabel_expr_collation(tle->expr, loccontext->collation);
+ }
+ }
+ else
+ {
+ /*
+ * For this case, we do the direct args (if any) together, as is
+ * normal for functions, but args which are either used only for
+ * sorting or are only part of a WITHIN GROUP are processed
+ * individually.
+ */
+
+ (void) assign_collations_walker((Node *) aggref->orddirectargs,
+ loccontext);
+
+ foreach(lc, aggref->args)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+ Assert(IsA(tle, TargetEntry));
+ if (tle->resjunk)
+ assign_expr_collations(context->pstate,
+ (Node *) tle);
+ else
+ (void) assign_collations_walker((Node *) tle,
+ loccontext);
+ }
+ }
+}
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 68b711d..1800a68 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -463,8 +463,8 @@ transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
newresult = ParseFuncOrColumn(pstate,
list_make1(n),
list_make1(result),
- NIL, NULL, false, false, false,
- NULL, true, location);
+ location,
+ NULL);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
@@ -631,8 +631,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
node = ParseFuncOrColumn(pstate,
list_make1(makeString(colname)),
list_make1(node),
- NIL, NULL, false, false, false,
- NULL, true, cref->location);
+ cref->location, NULL);
}
break;
}
@@ -676,8 +675,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
node = ParseFuncOrColumn(pstate,
list_make1(makeString(colname)),
list_make1(node),
- NIL, NULL, false, false, false,
- NULL, true, cref->location);
+ cref->location, NULL);
}
break;
}
@@ -734,8 +732,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
node = ParseFuncOrColumn(pstate,
list_make1(makeString(colname)),
list_make1(node),
- NIL, NULL, false, false, false,
- NULL, true, cref->location);
+ cref->location, NULL);
}
break;
}
@@ -1242,38 +1239,20 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
{
List *targs;
ListCell *args;
- Expr *tagg_filter;
/* Transform the list of arguments ... */
targs = NIL;
foreach(args, fn->args)
{
- targs = lappend(targs, transformExprRecurse(pstate,
- (Node *) lfirst(args)));
+ targs = lappend(targs, transformExprRecurse(pstate, (Node *) lfirst(args)));
}
- /*
- * Transform the aggregate filter using transformWhereClause(), to which
- * FILTER is virtually identical...
- */
- tagg_filter = NULL;
- if (fn->agg_filter != NULL)
- tagg_filter = (Expr *)
- transformWhereClause(pstate, (Node *) fn->agg_filter,
- EXPR_KIND_FILTER, "FILTER");
-
/* ... and hand off to ParseFuncOrColumn */
return ParseFuncOrColumn(pstate,
fn->funcname,
targs,
- fn->agg_order,
- tagg_filter,
- fn->agg_star,
- fn->agg_distinct,
- fn->func_variadic,
- fn->over,
- false,
- fn->location);
+ fn->location,
+ fn);
}
static Node *
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index ede36d1..456396f 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -17,16 +17,19 @@
#include "access/htup_details.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_aggregate.h"
#include "funcapi.h"
#include "lib/stringinfo.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
#include "parser/parse_type.h"
+#include "parser/parse_expr.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
@@ -56,15 +59,21 @@ static Node *ParseComplexProjection(ParseState *pstate, char *funcname,
* Also, when is_column is true, we return NULL on failure rather than
* reporting a no-such-function error.
*
- * The argument expressions (in fargs) and filter must have been transformed
- * already. But the agg_order expressions, if any, have not been.
+ * The argument expressions (in fargs) must have been transformed
+ * already.
*/
Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
- List *agg_order, Expr *agg_filter,
- bool agg_star, bool agg_distinct, bool func_variadic,
- WindowDef *over, bool is_column, int location)
+ int location, FuncCall *fn)
{
+ List *agg_order = (fn ? fn->agg_order : NIL);
+ Expr *agg_filter = NULL;
+ bool agg_star = (fn ? fn->agg_star : false);
+ bool agg_distinct = (fn ? fn->agg_distinct : false);
+ bool agg_within_group = (fn ? fn->has_within_group : false);
+ bool func_variadic = (fn ? fn->func_variadic : false);
+ WindowDef *over = (fn ? fn->over : NULL);
+ bool is_column = (fn == NULL);
Oid rettype;
Oid funcid;
ListCell *l;
@@ -81,6 +90,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
int nvargs;
Oid vatype;
FuncDetailCode fdresult;
+ int number_of_args = -1;
+ bool isordsetfunc = false;
+ bool ishypotheticalsetfunc = false;
+
+ /* Check if the function has WITHIN GROUP as well as distinct. */
+ Assert(!(agg_within_group && agg_distinct));
/*
* Most of the rest of the parser just assumes that functions do not have
@@ -98,6 +113,15 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
parser_errposition(pstate, location)));
/*
+ * Transform the aggregate filter using transformWhereClause(), to which
+ * FILTER is virtually identical...
+ */
+ if (fn && fn->agg_filter != NULL)
+ agg_filter = (Expr *)
+ transformWhereClause(pstate, (Node *) fn->agg_filter,
+ EXPR_KIND_FILTER, "FILTER");
+
+ /*
* Extract arg type info in preparation for function lookup.
*
* If any arguments are Param markers of type VOID, we discard them from
@@ -163,6 +187,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
}
}
+ if (agg_within_group && argnames)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ordered set functions cannot use named arguments"),
+ parser_errposition(pstate, location)));
+
if (fargs)
{
first_arg = linitial(fargs);
@@ -170,6 +200,26 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
}
/*
+ * If WITHIN GROUP is present, we need to call transformExpr on each
+ * SortBy node in agg_order, then call exprType and append to
+ * actual_arg_types, in order to get the types of values in WITHIN GROUP
+ * clause.
+ */
+ if (agg_within_group)
+ {
+ Assert(agg_order != NIL);
+
+ foreach(l, agg_order)
+ {
+ SortBy *arg = (SortBy *) lfirst(l);
+
+ arg->node = transformExpr(pstate, arg->node, EXPR_KIND_ORDER_BY);
+
+ actual_arg_types[nargs++] = exprType(arg->node);
+ }
+ }
+
+ /*
* Check for column projection: if function has one argument, and that
* argument is of complex type, and function name is not qualified, then
* the "function call" could be a projection. We also check that there
@@ -247,6 +297,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
errmsg("DISTINCT specified, but %s is not an aggregate function",
NameListToString(funcname)),
parser_errposition(pstate, location)));
+ if (agg_within_group)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("WITHIN GROUP specified, but %s is not an ordered set function",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
if (agg_order != NIL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -266,6 +322,53 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
NameListToString(funcname)),
parser_errposition(pstate, location)));
}
+ else if (fdresult == FUNCDETAIL_AGGREGATE)
+ {
+ HeapTuple tup;
+ Form_pg_aggregate classForm;
+
+ tup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for aggregate %u", funcid);
+
+ classForm = (Form_pg_aggregate) GETSTRUCT(tup);
+ isordsetfunc = classForm->aggisordsetfunc;
+
+ if (isordsetfunc)
+ {
+ if (classForm->aggordnargs == -2)
+ {
+ ishypotheticalsetfunc = true;
+
+ if (nvargs != 2*list_length(agg_order))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("function %s has %d ordering columns but %d hypothetical arguments",
+ NameListToString(funcname), list_length(agg_order), (nvargs - list_length(agg_order))),
+ parser_errposition(pstate, location)));
+ }
+ else
+ {
+ number_of_args = classForm->aggordnargs;
+ }
+
+ if (!agg_within_group)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("WITHIN GROUP is required for call to ordered set function %s",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
+
+ if (over)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("OVER clause not supported for call to ordered set function %s",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
+ }
+
+ ReleaseSysCache(tup);
+ }
else if (!(fdresult == FUNCDETAIL_AGGREGATE ||
fdresult == FUNCDETAIL_WINDOWFUNC))
{
@@ -351,13 +454,16 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
false);
/* perform the necessary typecasting of arguments */
- make_fn_arguments(pstate, fargs, actual_arg_types, declared_arg_types);
+ make_fn_arguments(pstate, fargs, (isordsetfunc) ? agg_order : NIL,
+ actual_arg_types,
+ declared_arg_types,
+ ishypotheticalsetfunc);
/*
* If it's a variadic function call, transform the last nvargs arguments
* into an array --- unless it's an "any" variadic.
*/
- if (nvargs > 0 && declared_arg_types[nargs - 1] != ANYOID)
+ if (nvargs > 0 && vatype != ANYOID)
{
ArrayExpr *newa = makeNode(ArrayExpr);
int non_var_args = nargs - nvargs;
@@ -388,16 +494,31 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
* When function is called with an explicit VARIADIC labeled parameter,
* and the declared_arg_type is "any", then sanity check the actual
* parameter type now - it must be an array.
+ *
+ * Also, it can't be a hypothetical set function, and if it's an ordered
+ * set function, the variadic labeled parameter is the last _direct_ arg,
+ * not an ordered arg. (In practice we're unlikely to get this far for
+ * hypotheticals, since make_fn_arguments would probably fail to unify
+ * types, but we can't change the order of these.)
*/
if (nargs > 0 && vatype == ANYOID && func_variadic)
{
- Oid va_arr_typid = actual_arg_types[nargs - 1];
+ int ignore_args = (agg_within_group ? list_length(agg_order) : 0);
+ Oid va_arr_typid = actual_arg_types[nargs - 1 - ignore_args];
+
+ if (ishypotheticalsetfunc)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("explicit VARIADIC argument not allowed for hypothetical set function"),
+ parser_errposition(pstate,
+ exprLocation((Node *) list_nth(fargs, nargs - 1 - ignore_args)))));
if (!OidIsValid(get_element_type(va_arr_typid)))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("VARIADIC argument must be an array"),
- parser_errposition(pstate, exprLocation((Node *) llast(fargs)))));
+ parser_errposition(pstate,
+ exprLocation((Node *) list_nth(fargs, nargs - 1 - ignore_args)))));
}
/* build the appropriate output structure */
@@ -421,6 +542,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
/* aggregate function */
Aggref *aggref = makeNode(Aggref);
+ if (agg_within_group && !isordsetfunc)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("%s is not an ordered set function",
+ func_signature_string(funcname, nargs, NIL, actual_arg_types))));
+
aggref->aggfnoid = funcid;
aggref->aggtype = rettype;
/* aggcollid and inputcollid will be set by parse_collate.c */
@@ -428,14 +555,24 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
aggref->aggfilter = agg_filter;
aggref->aggstar = agg_star;
aggref->aggvariadic = func_variadic;
+ aggref->ishypothetical = ishypotheticalsetfunc;
/* agglevelsup will be set by transformAggregateCall */
aggref->location = location;
+ if (isordsetfunc
+ && number_of_args >= 0
+ && number_of_args != list_length(fargs))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("incorrect number of direct arguments to ordered set function %s",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
+
/*
* Reject attempt to call a parameterless aggregate without (*)
* syntax. This is mere pedantry but some folks insisted ...
*/
- if (fargs == NIL && !agg_star)
+ if (fargs == NIL && !agg_star && !agg_within_group)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("%s(*) must be used to call a parameterless aggregate function",
@@ -464,7 +601,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
parser_errposition(pstate, location)));
/* parse_agg.c does additional aggregate-specific processing */
- transformAggregateCall(pstate, aggref, fargs, agg_order, agg_distinct);
+ transformAggregateCall(pstate, aggref, fargs, agg_order,
+ agg_distinct, agg_within_group);
retval = (Node *) aggref;
}
@@ -473,6 +611,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
/* window function */
WindowFunc *wfunc = makeNode(WindowFunc);
+ if (agg_within_group)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("WITHIN GROUP not allowed in window functions"),
+ parser_errposition(pstate, location)));
+
/*
* True window functions must be called with a window definition.
*/
@@ -1363,11 +1507,21 @@ func_get_detail(List *funcname,
void
make_fn_arguments(ParseState *pstate,
List *fargs,
+ List *agg_order,
Oid *actual_arg_types,
- Oid *declared_arg_types)
+ Oid *declared_arg_types,
+ bool requiresUnification)
{
ListCell *current_fargs;
+ ListCell *current_aoargs;
int i = 0;
+ int unify_offset = -1;
+
+ if (requiresUnification)
+ {
+ unify_offset = list_length(fargs) - list_length(agg_order);
+ Assert(unify_offset >= 0);
+ }
foreach(current_fargs, fargs)
{
@@ -1375,6 +1529,7 @@ make_fn_arguments(ParseState *pstate,
if (actual_arg_types[i] != declared_arg_types[i])
{
Node *node = (Node *) lfirst(current_fargs);
+ Node *temp = NULL;
/*
* If arg is a NamedArgExpr, coerce its input expr instead --- we
@@ -1395,18 +1550,66 @@ make_fn_arguments(ParseState *pstate,
}
else
{
- node = coerce_type(pstate,
- node,
- actual_arg_types[i],
- declared_arg_types[i], -1,
- COERCION_IMPLICIT,
- COERCE_IMPLICIT_CAST,
- -1);
- lfirst(current_fargs) = node;
+ /*
+ * If we are dealing with a hypothetical set function, we
+ * need to unify agg_order and fargs.
+ */
+
+ if (declared_arg_types[i] == ANYOID && requiresUnification)
+ {
+ Oid unification_oid;
+ SortBy *unify_with = (SortBy *) list_nth(agg_order,i - unify_offset);
+
+ unification_oid = select_common_type(pstate,
+ list_make2(unify_with->node,node),
+ "WITHIN GROUP",
+ NULL);
+
+ declared_arg_types[i + list_length(agg_order)] = unification_oid;
+
+ temp = coerce_type(pstate,
+ node,
+ actual_arg_types[i],
+ unification_oid, -1,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ }
+ else
+ {
+ temp = coerce_type(pstate,
+ node,
+ actual_arg_types[i],
+ declared_arg_types[i], -1,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ }
+
+ lfirst(current_fargs) = temp;
}
}
i++;
}
+
+ foreach(current_aoargs, agg_order)
+ {
+ if (actual_arg_types[i] != declared_arg_types[i])
+ {
+ SortBy *node = (SortBy *) lfirst(current_aoargs);
+ Node *temp = NULL;
+
+ temp = coerce_type(pstate,
+ node->node,
+ actual_arg_types[i],
+ declared_arg_types[i], -1,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ node->node = temp;
+ }
+ i++;
+ }
}
/*
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index dd80fa9..08cbabd 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -823,7 +823,7 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
false);
/* perform the necessary typecasting of arguments */
- make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
+ make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types, false);
/* and build the expression node */
result = makeNode(OpExpr);
@@ -953,7 +953,7 @@ make_scalar_array_op(ParseState *pstate, List *opname,
declared_arg_types[1] = res_atypeId;
/* perform the necessary typecasting of arguments */
- make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
+ make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types, false);
/* and build the expression node */
result = makeNode(ScalarArrayOpExpr);
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 41a8982..8ab553e 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -19,13 +19,13 @@ OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
array_userfuncs.o arrayutils.o bool.o \
cash.o char.o date.o datetime.o datum.o domains.o \
enum.o float.o format_type.o \
- geo_ops.o geo_selfuncs.o int.o int8.o json.o jsonfuncs.o like.o \
+ geo_ops.o geo_selfuncs.o hypotheticalset.o int.o int8.o json.o jsonfuncs.o like.o \
lockfuncs.o misc.o nabstime.o name.o numeric.o numutils.o \
oid.o oracle_compat.o pseudotypes.o rangetypes.o rangetypes_gist.o \
rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \
tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \
network.o mac.o inet_cidr_ntop.o inet_net_pton.o \
- ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \
+ inversedistribution.o ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \
ascii.o quote.o pgstatfuncs.o encode.o dbsize.o genfile.o trigfuncs.o \
tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
diff --git a/src/backend/utils/adt/hypotheticalset.c b/src/backend/utils/adt/hypotheticalset.c
new file mode 100644
index 0000000..1c9fd81
--- /dev/null
+++ b/src/backend/utils/adt/hypotheticalset.c
@@ -0,0 +1,223 @@
+/*-------------------------------------------------------------------------
+ *
+ * hypotheticalset.c
+ * Hypothetical set functions.
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/hypotheticalset.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "fmgr.h"
+#include <string.h>
+#include <math.h>
+
+#include "utils/tuplesort.h"
+#include "catalog/pg_type.h"
+#include "utils/datetime.h"
+#include "utils/builtins.h"
+#include "executor/executor.h"
+
+Datum hypothetical_rank_final(PG_FUNCTION_ARGS);
+Datum hypothetical_dense_rank_final(PG_FUNCTION_ARGS);
+Datum hypothetical_percent_rank_final(PG_FUNCTION_ARGS);
+Datum hypothetical_cume_dist_final(PG_FUNCTION_ARGS);
+
+
+/*
+ * Common code to sanity-check args for hypothetical set functions. No need
+ * for friendly errors, these can only happen if someone's messing up the
+ * aggregate definitions. The checks are needed for security, however; but we
+ * only need them once per call site. Store a pointer to the tupdesc as a
+ * sentinel.
+ */
+
+static void
+hypothetical_check_argtypes(FunctionCallInfo fcinfo, int nargs, TupleDesc tupdesc)
+{
+ int i;
+
+ if (!tupdesc
+ || (nargs + 1) != tupdesc->natts
+ || tupdesc->attrs[nargs]->atttypid != BOOLOID)
+ elog(ERROR, "type mismatch in hypothetical set function");
+
+ for (i = 0; i < nargs; ++i)
+ if (get_fn_expr_argtype(fcinfo->flinfo,i) != tupdesc->attrs[i]->atttypid)
+ elog(ERROR, "type mismatch in hypothetical set function");
+
+ fcinfo->flinfo->fn_extra = tupdesc;
+}
+
+/*
+ * rank(float8) - rank of hypothetical row
+ */
+Datum
+hypothetical_rank_final(PG_FUNCTION_ARGS)
+{
+ Tuplesortstate *sorter = NULL;
+ TupleDesc tupdesc = NULL;
+ TupleTableSlot *slot = NULL;
+ Oid datumtype = InvalidOid;
+ int nargs = PG_NARGS();
+ int i;
+ int64 rank = 1;
+
+ AggSetGetSortInfo(fcinfo, &sorter, &tupdesc, &slot, &datumtype);
+
+ if (fcinfo->flinfo->fn_extra == NULL
+ || fcinfo->flinfo->fn_extra != tupdesc)
+ hypothetical_check_argtypes(fcinfo, nargs, tupdesc);
+
+ /* insert the hypothetical row into the sort */
+
+ ExecClearTuple(slot);
+ for (i = 0; i < nargs; ++i)
+ {
+ slot->tts_values[i] = PG_GETARG_DATUM(i);
+ slot->tts_isnull[i] = PG_ARGISNULL(i);
+ }
+ slot->tts_values[nargs] = BoolGetDatum(true);
+ slot->tts_isnull[nargs] = false;
+ ExecStoreVirtualTuple(slot);
+
+ tuplesort_puttupleslot(sorter, slot);
+
+ tuplesort_performsort(sorter);
+
+ while (tuplesort_gettupleslot(sorter, true, slot))
+ {
+ bool isnull;
+ Datum d = slot_getattr(slot, nargs + 1, &isnull);
+
+ if (!isnull && DatumGetBool(d))
+ break;
+
+ ++rank;
+ }
+
+ ExecClearTuple(slot);
+
+ PG_RETURN_INT64(rank);
+}
+
+/*
+ * dense_rank(float8) - rank of hypothetical row
+ * without gap in ranking
+ */
+Datum
+hypothetical_dense_rank_final(PG_FUNCTION_ARGS)
+{
+ Tuplesortstate *sorter = NULL;
+ TupleDesc tupdesc = NULL;
+ TupleTableSlot *slot = NULL;
+ Oid datumtype = InvalidOid;
+ int nargs = PG_NARGS();
+ int i;
+ int64 rank = 1;
+ int duplicate_count = 0;
+ TupleTableSlot *slot2 = NULL;
+ AttrNumber *colidx;
+ FmgrInfo *equalfns;
+ int numDistinctCol = 0;
+ MemoryContext memcontext;
+
+ AggSetGetSortInfo(fcinfo, &sorter, &tupdesc, &slot, &datumtype);
+
+ if (fcinfo->flinfo->fn_extra == NULL
+ || fcinfo->flinfo->fn_extra != tupdesc)
+ hypothetical_check_argtypes(fcinfo, nargs, tupdesc);
+
+ /* insert the hypothetical row into the sort */
+
+ ExecClearTuple(slot);
+ for (i = 0; i < nargs; ++i)
+ {
+ slot->tts_values[i] = PG_GETARG_DATUM(i);
+ slot->tts_isnull[i] = PG_ARGISNULL(i);
+ }
+ slot->tts_values[nargs] = BoolGetDatum(true);
+ slot->tts_isnull[nargs] = false;
+ ExecStoreVirtualTuple(slot);
+
+ tuplesort_puttupleslot(sorter, slot);
+
+ tuplesort_performsort(sorter);
+
+ numDistinctCol = AggSetGetDistinctInfo(fcinfo, &slot2, &colidx, &equalfns);
+
+ ExecClearTuple(slot2);
+
+ AggSetGetPerTupleContext(fcinfo, &memcontext);
+
+ while (tuplesort_gettupleslot(sorter, true, slot))
+ {
+ TupleTableSlot *tmpslot = slot2;
+ bool isnull;
+ Datum d = slot_getattr(slot, nargs + 1, &isnull);
+
+ if (!isnull && DatumGetBool(d))
+ break;
+
+ if (!TupIsNull(slot2)
+ && execTuplesMatch(slot, slot2,
+ (numDistinctCol - 1),
+ colidx,
+ equalfns,
+ memcontext))
+ ++duplicate_count;
+
+ slot2 = slot;
+ slot = tmpslot;
+
+ ++rank;
+ }
+
+ ExecClearTuple(slot);
+ ExecClearTuple(slot2);
+
+ rank = rank - duplicate_count;
+ PG_RETURN_INT64(rank);
+}
+
+/* percent_rank(float8)
+ * Calculates the relative ranking of hypothetical
+ * row within a group
+ */
+
+Datum
+hypothetical_percent_rank_final(PG_FUNCTION_ARGS)
+{
+ Datum rank = hypothetical_rank_final(fcinfo);
+ int64 rank_val = DatumGetInt64(rank);
+ int64 rowcount = AggSetGetRowCount(fcinfo) + 1;
+ float8 result_val = 0.0;
+
+ if (rowcount == 1)
+ PG_RETURN_FLOAT8(0);
+
+ result_val = (float8) (rank_val - 1) / (float8) (rowcount - 1);
+
+ PG_RETURN_FLOAT8(result_val);
+}
+
+/* cume_dist - cumulative distribution of hypothetical
+ * row in a group
+ */
+
+Datum
+hypothetical_cume_dist_final(PG_FUNCTION_ARGS)
+{
+ Datum rank = hypothetical_rank_final(fcinfo);
+ int64 rank_val = DatumGetInt64(rank);
+ int64 rowcount = AggSetGetRowCount(fcinfo) + 1;
+
+ float8 result_val = (float8) (rank_val) / (float8) (rowcount);
+
+ PG_RETURN_FLOAT8(result_val);
+}
diff --git a/src/backend/utils/adt/inversedistribution.c b/src/backend/utils/adt/inversedistribution.c
new file mode 100644
index 0000000..fdda722
--- /dev/null
+++ b/src/backend/utils/adt/inversedistribution.c
@@ -0,0 +1,662 @@
+/*-------------------------------------------------------------------------
+ *
+ * inversedistribution.c
+ * Inverse distribution functions.
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/inversedistribution.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "fmgr.h"
+#include <string.h>
+#include <math.h>
+
+#include "utils/tuplesort.h"
+#include "catalog/pg_type.h"
+#include "utils/datetime.h"
+#include "utils/lsyscache.h"
+#include "utils/array.h"
+
+/*
+ * percentile_disc(float8) - discrete percentile
+ */
+
+Datum percentile_disc_final(PG_FUNCTION_ARGS);
+
+Datum
+percentile_disc_final(PG_FUNCTION_ARGS)
+{
+ float8 percentile;
+ Tuplesortstate *sorter;
+ Oid datumtype;
+ Datum val;
+ bool isnull;
+ int64 skiprows;
+ int64 rowcount = AggSetGetRowCount(fcinfo);
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+
+ percentile = PG_GETARG_FLOAT8(0);
+
+ AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+
+ if (percentile < 0 || percentile > 1 || isnan(percentile))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("percentile value %g must be between 0 and 1", percentile)));
+
+ if (rowcount < 1)
+ PG_RETURN_NULL();
+
+ tuplesort_performsort(sorter);
+
+ /*
+ * We need the smallest K such that (K/N) >= percentile. K starts at 1.
+ * Therefore K >= N*percentile
+ * Therefore K = ceil(N*percentile)
+ * So we skip K-1 rows (if K>0) and return the next row fetched.
+ *
+ * We don't actually expect to see nulls in the input, our strict flag
+ * should have filtered them out, but we're required to not crash if
+ * there is one.
+ */
+
+ skiprows = (int64) ceil(percentile * rowcount);
+ Assert(skiprows <= rowcount);
+
+ while (--skiprows > 0)
+ if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ elog(ERROR,"missing row in percentile_disc");
+
+ if (!tuplesort_getdatum(sorter, true, &val, &isnull))
+ elog(ERROR,"missing row in percentile_disc");
+
+ if (isnull)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_DATUM(val);
+}
+
+
+/*
+ * For percentile_cont, we need a way to interpolate between consecutive
+ * values. Use a helper function for that, so that we can share the rest
+ * of the code between types.
+ */
+
+static Datum float8_lerp(Datum lo, Datum hi, float8 pct)
+{
+ float8 loval = DatumGetFloat8(lo);
+ float8 hival = DatumGetFloat8(hi);
+ return Float8GetDatum(loval + (pct * (hival - loval)));
+}
+
+static Datum interval_lerp(Datum lo, Datum hi, float8 pct)
+{
+ Datum diff_result = DirectFunctionCall2(interval_mi, hi, lo);
+ Datum mul_result = DirectFunctionCall2(interval_mul,
+ diff_result,
+ Float8GetDatumFast(pct));
+ return DirectFunctionCall2(interval_pl, mul_result, lo);
+}
+
+typedef Datum (*LerpFunc)(Datum lo, Datum hi, float8 pct);
+
+static Datum
+percentile_cont_final_common(FunctionCallInfo fcinfo,
+ Oid expect_type,
+ LerpFunc lerpfunc)
+{
+ float8 percentile;
+ int64 rowcount = AggSetGetRowCount(fcinfo);
+ Tuplesortstate *sorter;
+ Oid datumtype;
+ Datum val;
+ Datum first_row;
+ Datum second_row;
+ float8 proportion;
+ bool isnull;
+ int64 skiprows;
+ int64 lower_row = 0;
+ int64 higher_row = 0;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+
+ percentile = PG_GETARG_FLOAT8(0);
+
+ AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+
+ Assert(datumtype == expect_type);
+
+ if (percentile < 0 || percentile > 1 || isnan(percentile))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("percentile value %g must be between 0 and 1", percentile)));
+
+ if (rowcount < 1)
+ PG_RETURN_NULL();
+
+ tuplesort_performsort(sorter);
+
+ lower_row = floor(percentile * (rowcount - 1));
+ higher_row = ceil(percentile * (rowcount - 1));
+
+ Assert(lower_row < rowcount);
+
+ for (skiprows = lower_row; skiprows > 0; --skiprows)
+ if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ elog(ERROR,"missing row in percentile_cont");
+
+ if (!tuplesort_getdatum(sorter, true, &first_row, &isnull))
+ elog(ERROR,"missing row in percentile_cont");
+ if (isnull)
+ PG_RETURN_NULL();
+
+ if (lower_row == higher_row)
+ {
+ val = first_row;
+ }
+ else
+ {
+ if (!tuplesort_getdatum(sorter, true, &second_row, &isnull))
+ elog(ERROR,"missing row in percentile_cont");
+
+ if (isnull)
+ PG_RETURN_NULL();
+
+ proportion = (percentile * (rowcount-1)) - lower_row;
+ val = lerpfunc(first_row, second_row, proportion);
+ }
+
+ if (isnull)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_DATUM(val);
+}
+
+
+
+/*
+ * percentile_cont(float8) - continuous percentile
+ */
+
+Datum percentile_cont_float8_final(PG_FUNCTION_ARGS);
+Datum percentile_cont_interval_final(PG_FUNCTION_ARGS);
+
+Datum
+percentile_cont_float8_final(PG_FUNCTION_ARGS)
+{
+ return percentile_cont_final_common(fcinfo, FLOAT8OID, float8_lerp);
+}
+
+/*
+ * percentile_interval_cont(Interval) - continuous percentile for Interval
+ */
+
+Datum
+percentile_cont_interval_final(PG_FUNCTION_ARGS)
+{
+ return percentile_cont_final_common(fcinfo, INTERVALOID, interval_lerp);
+}
+
+
+/*
+ * mode() - most common value
+ */
+
+Datum mode_final(PG_FUNCTION_ARGS);
+
+Datum
+mode_final(PG_FUNCTION_ARGS)
+{
+ Tuplesortstate *sorter;
+ Oid datumtype;
+ bool isnull;
+ Datum val;
+ Datum last_val = (Datum) 0;
+ bool last_val_is_mode = false;
+ int64 val_freq = 0;
+ Datum mode_val = (Datum) 0;
+ int64 mode_freq = 0;
+ FmgrInfo *equalfn;
+ bool shouldfree;
+
+ struct mode_type_info {
+ Oid typid;
+ int16 typLen;
+ bool typByVal;
+ } *typinfo = fcinfo->flinfo->fn_extra;
+
+ AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ AggSetGetDistinctInfo(fcinfo, NULL, NULL, &equalfn);
+
+ if (!typinfo || typinfo->typid != datumtype)
+ {
+ if (typinfo)
+ pfree(typinfo);
+ typinfo = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ sizeof(struct mode_type_info));
+ typinfo->typid = datumtype;
+ get_typlenbyval(datumtype, &typinfo->typLen, &typinfo->typByVal);
+ }
+
+ shouldfree = !(typinfo->typByVal);
+
+ tuplesort_performsort(sorter);
+
+ while (tuplesort_getdatum(sorter, true, &val, &isnull))
+ {
+ if (isnull)
+ continue;
+
+ if (val_freq == 0)
+ {
+ /* first value - assume modal until shown otherwise */
+ mode_val = last_val = val;
+ mode_freq = val_freq = 1;
+ last_val_is_mode = true;
+ }
+ else if (DatumGetBool(FunctionCall2(equalfn, val, last_val)))
+ {
+ /* value equal to previous value */
+ if (last_val_is_mode)
+ ++mode_freq;
+ else if (++val_freq > mode_freq)
+ {
+ if (shouldfree)
+ {
+ pfree(DatumGetPointer(mode_val));
+ pfree(DatumGetPointer(val));
+ }
+
+ mode_val = last_val;
+ mode_freq = val_freq;
+ last_val_is_mode = true;
+ }
+ else if (shouldfree)
+ pfree(DatumGetPointer(val));
+ }
+ else
+ {
+ if (shouldfree && !last_val_is_mode)
+ pfree(DatumGetPointer(last_val));
+
+ last_val_is_mode = false;
+ last_val = val;
+ val_freq = 1;
+ }
+ }
+
+ if (shouldfree && !last_val_is_mode)
+ pfree(DatumGetPointer(last_val));
+
+ if (mode_freq)
+ PG_RETURN_DATUM(mode_val);
+ else
+ PG_RETURN_NULL();
+}
+
+
+
+/*
+ * percentile_disc(float8[]) - discrete percentiles
+ */
+
+Datum percentile_disc_multi_final(PG_FUNCTION_ARGS);
+
+struct pct_info {
+ int64 first_row;
+ int64 second_row;
+ float8 proportion;
+ int idx;
+};
+
+static int pct_info_cmp(const void *pa, const void *pb)
+{
+ const struct pct_info *a = pa;
+ const struct pct_info *b = pb;
+ if (a->first_row == b->first_row)
+ return (a->second_row < b->second_row) ? -1 : (a->second_row == b->second_row) ? 0 : 1;
+ else
+ return (a->first_row < b->first_row) ? -1 : 1;
+}
+
+static struct pct_info *setup_pct_info(int num_percentiles,
+ Datum *percentiles_datum,
+ bool *percentiles_null,
+ int64 rowcount,
+ bool continuous)
+{
+ struct pct_info *pct_info = palloc(num_percentiles * sizeof(struct pct_info));
+ int i;
+
+ for (i = 0; i < num_percentiles; i++)
+ {
+ pct_info[i].idx = i;
+
+ if (percentiles_null[i])
+ {
+ pct_info[i].first_row = 0;
+ pct_info[i].second_row = 0;
+ pct_info[i].proportion = 0;
+ }
+ else
+ {
+ float8 p = DatumGetFloat8(percentiles_datum[i]);
+
+ if (p < 0 || p > 1 || isnan(p))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("percentile value %g must be between 0 and 1", p)));
+
+ if (continuous)
+ {
+ pct_info[i].first_row = 1 + floor(p * (rowcount - 1));
+ pct_info[i].second_row = 1 + ceil(p * (rowcount - 1));
+ pct_info[i].proportion = (p * (rowcount-1)) - floor(p * (rowcount-1));
+ }
+ else
+ {
+ /*
+ * We need the smallest K such that (K/N) >= percentile. K starts at 1.
+ * Therefore K >= N*percentile
+ * Therefore K = ceil(N*percentile), minimum 1
+ */
+
+ pct_info[i].first_row = Max(1, (int64) ceil(rowcount * p));
+ pct_info[i].second_row = 0;
+ pct_info[i].proportion = 0;
+ }
+ }
+ }
+
+ qsort(pct_info, num_percentiles, sizeof(struct pct_info), pct_info_cmp);
+
+ return pct_info;
+}
+
+Datum
+percentile_disc_multi_final(PG_FUNCTION_ARGS)
+{
+ ArrayType *param;
+ Datum *percentiles_datum;
+ bool *percentiles_null;
+ int num_percentiles;
+ int64 rowcount = AggSetGetRowCount(fcinfo);
+ int64 rownum = 0;
+ Tuplesortstate *sorter;
+ Oid datumtype;
+ Datum val;
+ bool isnull;
+ Datum *result_datum;
+ bool *result_isnull;
+ int i;
+ struct pct_info *pct_info;
+
+ struct mode_type_info {
+ Oid typid;
+ int16 typLen;
+ bool typByVal;
+ char typAlign;
+ } *typinfo = fcinfo->flinfo->fn_extra;
+
+ if (PG_ARGISNULL(0) || rowcount < 1)
+ PG_RETURN_NULL();
+
+ AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+
+ if (!typinfo || typinfo->typid != datumtype)
+ {
+ if (typinfo)
+ pfree(typinfo);
+ typinfo = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ sizeof(struct mode_type_info));
+ typinfo->typid = datumtype;
+ get_typlenbyvalalign(datumtype,
+ &typinfo->typLen,
+ &typinfo->typByVal,
+ &typinfo->typAlign);
+ }
+
+ param = PG_GETARG_ARRAYTYPE_P(0);
+
+ deconstruct_array(param, FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd',
+ &percentiles_datum, &percentiles_null, &num_percentiles);
+
+ if (num_percentiles == 0)
+ PG_RETURN_POINTER(construct_empty_array(datumtype));
+
+ result_datum = palloc0(num_percentiles * sizeof(Datum));
+ result_isnull = palloc0(num_percentiles * sizeof(bool));
+
+ pct_info = setup_pct_info(num_percentiles,
+ percentiles_datum,
+ percentiles_null,
+ rowcount,
+ false);
+
+ /*
+ * Start by dealing with any nulls in the param array - those are
+ * sorted to the front on row=0, so set the corresponding result
+ * indexes to null
+ */
+ for (i = 0; i < num_percentiles; ++i)
+ {
+ int idx = pct_info[i].idx;
+
+ if (pct_info[i].first_row > 0)
+ break;
+
+ result_datum[idx] = (Datum) 0;
+ result_isnull[idx] = true;
+ }
+
+ /*
+ * If there's anything left after doing the nulls, then grind the
+ * input and extract the needed values
+ */
+ if (i < num_percentiles)
+ {
+ tuplesort_performsort(sorter);
+
+ for (; i < num_percentiles; ++i)
+ {
+ int64 target_row = pct_info[i].first_row;
+ int idx = pct_info[i].idx;
+
+ if (target_row > rownum)
+ {
+ while (target_row > ++rownum)
+ {
+ if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ elog(ERROR,"missing row in percentile_disc");
+ }
+
+ if (!tuplesort_getdatum(sorter, true, &val, &isnull))
+ elog(ERROR,"missing row in percentile_disc");
+ }
+
+ result_datum[idx] = val;
+ result_isnull[idx] = isnull;
+ }
+ }
+
+ /* We make the output array the same shape as the input */
+
+ PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull,
+ ARR_NDIM(param),
+ ARR_DIMS(param), ARR_LBOUND(param),
+ datumtype,
+ typinfo->typLen,
+ typinfo->typByVal,
+ typinfo->typAlign));
+}
+
+static Datum
+percentile_cont_multi_final_common(FunctionCallInfo fcinfo,
+ Oid expect_type,
+ int16 typLen, bool typByVal, char typAlign,
+ LerpFunc lerpfunc)
+{
+ ArrayType *param;
+ Datum *percentiles_datum;
+ bool *percentiles_null;
+ int num_percentiles;
+ int64 rowcount = AggSetGetRowCount(fcinfo);
+ int64 rownum = 0;
+ int64 rownum_second = 0;
+ Tuplesortstate *sorter;
+ Oid datumtype;
+ Datum first_val;
+ Datum second_val;
+ bool isnull;
+ Datum *result_datum;
+ bool *result_isnull;
+ int i;
+ struct pct_info *pct_info;
+
+ if (PG_ARGISNULL(0) || rowcount < 1)
+ PG_RETURN_NULL();
+
+ AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ Assert(datumtype == expect_type);
+
+ param = PG_GETARG_ARRAYTYPE_P(0);
+
+ deconstruct_array(param, FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd',
+ &percentiles_datum, &percentiles_null, &num_percentiles);
+
+ if (num_percentiles == 0)
+ PG_RETURN_POINTER(construct_empty_array(datumtype));
+
+ result_datum = palloc0(num_percentiles * sizeof(Datum));
+ result_isnull = palloc0(num_percentiles * sizeof(bool));
+
+ pct_info = setup_pct_info(num_percentiles,
+ percentiles_datum,
+ percentiles_null,
+ rowcount,
+ true);
+
+ /*
+ * Start by dealing with any nulls in the param array - those are
+ * sorted to the front on row=0, so set the corresponding result
+ * indexes to null
+ */
+ for (i = 0; i < num_percentiles; ++i)
+ {
+ int idx = pct_info[i].idx;
+
+ if (pct_info[i].first_row > 0)
+ break;
+
+ result_datum[idx] = (Datum) 0;
+ result_isnull[idx] = true;
+ }
+
+ /*
+ * If there's anything left after doing the nulls, then grind the
+ * input and extract the needed values
+ */
+ if (i < num_percentiles)
+ {
+ tuplesort_performsort(sorter);
+
+ for (; i < num_percentiles; ++i)
+ {
+ int64 target_row = pct_info[i].first_row;
+ bool need_lerp = pct_info[i].second_row > target_row;
+ int idx = pct_info[i].idx;
+
+ if (target_row > rownum_second)
+ {
+ rownum = rownum_second;
+
+ while (target_row > ++rownum)
+ {
+ if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ elog(ERROR,"missing row in percentile_cont");
+ }
+
+ if (!tuplesort_getdatum(sorter, true, &first_val, &isnull) || isnull)
+ elog(ERROR,"missing row in percentile_cont");
+
+ rownum_second = rownum;
+
+ if (need_lerp)
+ {
+ if (!tuplesort_getdatum(sorter, true, &second_val, &isnull) || isnull)
+ elog(ERROR,"missing row in percentile_cont");
+ ++rownum_second;
+ }
+ }
+ else if (target_row == rownum_second)
+ {
+ first_val = second_val;
+ rownum = rownum_second;
+
+ if (need_lerp)
+ {
+ if (!tuplesort_getdatum(sorter, true, &second_val, &isnull) || isnull)
+ elog(ERROR,"missing row in percentile_cont");
+ ++rownum_second;
+ }
+ }
+
+ if (need_lerp)
+ {
+ result_datum[idx] = lerpfunc(first_val, second_val, pct_info[i].proportion);
+ }
+ else
+ result_datum[idx] = first_val;
+
+ result_isnull[idx] = false;
+ }
+ }
+
+ /* We make the output array the same shape as the input */
+
+ PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull,
+ ARR_NDIM(param),
+ ARR_DIMS(param), ARR_LBOUND(param),
+ expect_type,
+ typLen,
+ typByVal,
+ typAlign));
+}
+
+
+/*
+ * percentile_cont(float8[]) within group (float8) - continuous percentiles
+ */
+
+Datum percentile_cont_float8_multi_final(PG_FUNCTION_ARGS);
+Datum percentile_cont_interval_multi_final(PG_FUNCTION_ARGS);
+
+Datum
+percentile_cont_float8_multi_final(PG_FUNCTION_ARGS)
+{
+ return percentile_cont_multi_final_common(fcinfo,
+ FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd',
+ float8_lerp);
+}
+
+/*
+ * percentile_cont(float8[]) within group (Interval) - continuous percentiles
+ */
+
+Datum
+percentile_cont_interval_multi_final(PG_FUNCTION_ARGS)
+{
+ return percentile_cont_multi_final_common(fcinfo,
+ INTERVALOID, 16, false, 'd',
+ interval_lerp);
+}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 5ffce68..aa27fd0 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -22,6 +22,7 @@
#include "access/sysattr.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
+#include "catalog/pg_aggregate.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -293,6 +294,9 @@ static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
static int print_function_arguments(StringInfo buf, HeapTuple proctup,
bool print_table_args, bool print_defaults);
static void print_function_rettype(StringInfo buf, HeapTuple proctup);
+static void print_aggregate_arguments(StringInfo buf,
+ HeapTuple proctup, HeapTuple aggtup,
+ bool print_defaults);
static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
Bitmapset *rels_used);
static bool refname_is_unique(char *refname, deparse_namespace *dpns,
@@ -403,6 +407,8 @@ static char *generate_function_name(Oid funcid, int nargs,
static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
static text *string_to_text(char *str);
static char *flatten_reloptions(Oid relid);
+static void get_aggstd_expr(Aggref *aggref, deparse_context *context);
+static void get_ordset_expr(Aggref *aggref, deparse_context *context);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
@@ -2268,6 +2274,149 @@ print_function_arguments(StringInfo buf, HeapTuple proctup,
/*
+ * pg_get_aggregate_arguments
+ * Get a nicely-formatted list of arguments for an aggregate.
+ * This is everything that would go after the function name
+ * in CREATE AGGREGATE, _including_ the parens, because in the
+ * case of ordered set funcs, we emit the WITHIN GROUP clause
+ * too.
+ */
+Datum
+pg_get_aggregate_arguments(PG_FUNCTION_ARGS)
+{
+ Oid funcid = PG_GETARG_OID(0);
+ StringInfoData buf;
+ HeapTuple proctup;
+ HeapTuple aggtup;
+
+ initStringInfo(&buf);
+
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(proctup))
+ elog(ERROR, "cache lookup failed for function %u", funcid);
+
+ aggtup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(aggtup))
+ elog(ERROR, "function %u is not an aggregate function", funcid);
+
+ (void) print_aggregate_arguments(&buf, proctup, aggtup, true);
+
+ ReleaseSysCache(aggtup);
+ ReleaseSysCache(proctup);
+
+ PG_RETURN_TEXT_P(string_to_text(buf.data));
+}
+
+/*
+ * pg_get_aggregate_identity_arguments
+ * Get a formatted list of arguments for an aggregate.
+ * This is everything that would go after the function name in
+ * ALTER AGGREGATE, etc. In particular, don't print defaults.
+ * Currently, this is identical to pg_get_aggregate_arguments,
+ * but if we ever allow defaults that will change.
+ */
+Datum
+pg_get_aggregate_identity_arguments(PG_FUNCTION_ARGS)
+{
+ Oid funcid = PG_GETARG_OID(0);
+ StringInfoData buf;
+ HeapTuple proctup;
+ HeapTuple aggtup;
+
+ initStringInfo(&buf);
+
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(proctup))
+ elog(ERROR, "cache lookup failed for function %u", funcid);
+
+ aggtup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(aggtup))
+ elog(ERROR, "function %u is not an aggregate function", funcid);
+
+ (void) print_aggregate_arguments(&buf, proctup, aggtup, false);
+
+ ReleaseSysCache(aggtup);
+ ReleaseSysCache(proctup);
+
+ PG_RETURN_TEXT_P(string_to_text(buf.data));
+}
+
+
+/*
+ * Common code for pg_get_aggregate_arguments
+ * We print argument defaults only if print_defaults is true.
+ */
+static void
+print_aggregate_arguments(StringInfo buf,
+ HeapTuple proctup, HeapTuple aggtup,
+ bool print_defaults)
+{
+ Form_pg_aggregate agg = (Form_pg_aggregate) GETSTRUCT(aggtup);
+ int numargs;
+ bool ordsetfunc = agg->aggisordsetfunc;
+ int numdirectargs = agg->aggordnargs;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ int i;
+
+ /* defaults not supported at this time */
+ (void) print_defaults;
+
+ numargs = get_func_arg_info(proctup,
+ &argtypes, &argnames, &argmodes);
+
+ appendStringInfoChar(buf, '(');
+
+ for (i = 0; i < numargs; i++)
+ {
+ Oid argtype = argtypes[i];
+ char *argname = argnames ? argnames[i] : NULL;
+ char argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
+ const char *modename;
+
+ switch (argmode)
+ {
+ case PROARGMODE_IN:
+ modename = "";
+ break;
+ case PROARGMODE_VARIADIC:
+ modename = "VARIADIC ";
+ break;
+ default:
+ elog(ERROR, "invalid parameter mode '%c'", argmode);
+ modename = NULL; /* keep compiler quiet */
+ break;
+ }
+
+ if (i == numdirectargs)
+ {
+ appendStringInfoString(buf, ") WITHIN GROUP (");
+ }
+ else if (i > 0)
+ appendStringInfoString(buf, ", ");
+
+ appendStringInfoString(buf, modename);
+
+ if (argname && argname[0])
+ appendStringInfo(buf, "%s ", quote_identifier(argname));
+
+ appendStringInfoString(buf, format_type_be(argtype));
+ }
+
+ if (ordsetfunc)
+ {
+ if (numdirectargs < 0 || numdirectargs == numargs)
+ appendStringInfoString(buf, ") WITHIN GROUP (*");
+ }
+ else if (numargs == 0)
+ appendStringInfoChar(buf, '*');
+
+ appendStringInfoChar(buf, ')');
+}
+
+
+/*
* deparse_expression - General utility for deparsing expressions
*
* calls deparse_expression_pretty with all prettyPrinting disabled
@@ -7408,6 +7557,80 @@ static void
get_agg_expr(Aggref *aggref, deparse_context *context)
{
StringInfo buf = context->buf;
+
+ if (aggref->isordset)
+ {
+ get_ordset_expr(aggref, context);
+ }
+ else
+ {
+ get_aggstd_expr(aggref, context);
+ }
+
+ if (aggref->aggfilter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)aggref->aggfilter, context, false);
+ }
+
+ appendStringInfoString(buf, ")");
+}
+
+static void
+get_ordset_expr(Aggref *aggref, deparse_context *context)
+{
+ StringInfo buf = context->buf;
+ Oid argtypes[FUNC_MAX_ARGS];
+ List *arglist;
+ int nargs;
+ ListCell *l;
+
+ arglist = NIL;
+ nargs = 0;
+
+ foreach(l, aggref->orddirectargs)
+ {
+ Node *arg = (Node *) lfirst(l);
+
+ Assert(!IsA(arg, NamedArgExpr));
+ if (nargs >= FUNC_MAX_ARGS) /* paranoia */
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ errmsg("too many arguments")));
+ argtypes[nargs] = exprType(arg);
+ nargs++;
+ }
+
+ /* For direct arguments in case of ordered set functions */
+ foreach(l, aggref->args)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+ Node *arg = (Node *) tle->expr;
+
+ Assert(!IsA(arg, NamedArgExpr));
+ if (nargs >= FUNC_MAX_ARGS) /* paranoia */
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ errmsg("too many arguments")));
+ argtypes[nargs] = exprType(arg);
+ arglist = lappend(arglist, arg);
+ nargs++;
+ }
+
+ appendStringInfo(buf, "%s(",
+ generate_function_name(aggref->aggfnoid, nargs,
+ NIL, argtypes,
+ false, NULL));
+
+ get_rule_expr((Node *)aggref->orddirectargs, context, true);
+ appendStringInfoString(buf, ") WITHIN GROUP (ORDER BY ");
+ get_rule_orderby(aggref->aggorder, aggref->args, false, context);
+
+}
+static void
+get_aggstd_expr(Aggref *aggref, deparse_context *context)
+{
+ StringInfo buf = context->buf;
Oid argtypes[FUNC_MAX_ARGS];
List *arglist;
int nargs;
@@ -7462,14 +7685,6 @@ get_agg_expr(Aggref *aggref, deparse_context *context)
appendStringInfoString(buf, " ORDER BY ");
get_rule_orderby(aggref->aggorder, aggref->args, false, context);
}
-
- if (aggref->aggfilter != NULL)
- {
- appendStringInfoString(buf, ") FILTER (WHERE ");
- get_rule_expr((Node *) aggref->aggfilter, context, false);
- }
-
- appendStringInfoChar(buf, ')');
}
/*
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index ea8af9f..7375d8e 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -1411,23 +1411,29 @@ tuplesort_performsort(Tuplesortstate *state)
* Internal routine to fetch the next tuple in either forward or back
* direction into *stup. Returns FALSE if no more tuples.
* If *should_free is set, the caller must pfree stup.tuple when done with it.
+ * stup may be null to move without fetching.
*/
static bool
tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
SortTuple *stup, bool *should_free)
{
unsigned int tuplen;
+ SortTuple dummy;
+ SortTuple *ptup = stup ? stup : &dummy;
switch (state->status)
{
case TSS_SORTEDINMEM:
Assert(forward || state->randomAccess);
- *should_free = false;
+ if (should_free)
+ *should_free = false;
if (forward)
{
if (state->current < state->memtupcount)
{
- *stup = state->memtuples[state->current++];
+ if (stup)
+ *stup = state->memtuples[state->current];
+ state->current++;
return true;
}
state->eof_reached = true;
@@ -1459,21 +1465,25 @@ tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
if (state->current <= 0)
return false;
}
- *stup = state->memtuples[state->current - 1];
+ if (stup)
+ *stup = state->memtuples[state->current - 1];
return true;
}
break;
case TSS_SORTEDONTAPE:
Assert(forward || state->randomAccess);
- *should_free = true;
+ if (should_free)
+ *should_free = true;
if (forward)
{
if (state->eof_reached)
return false;
if ((tuplen = getlen(state, state->result_tape, true)) != 0)
{
- READTUP(state, stup, state->result_tape, tuplen);
+ READTUP(state, ptup, state->result_tape, tuplen);
+ if (!stup && dummy.tuple)
+ pfree(dummy.tuple);
return true;
}
else
@@ -1546,12 +1556,15 @@ tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
state->result_tape,
tuplen))
elog(ERROR, "bogus tuple length in backward scan");
- READTUP(state, stup, state->result_tape, tuplen);
+ READTUP(state, ptup, state->result_tape, tuplen);
+ if (!stup && dummy.tuple)
+ pfree(dummy.tuple);
return true;
case TSS_FINALMERGE:
Assert(forward);
- *should_free = true;
+ if (should_free)
+ *should_free = true;
/*
* This code should match the inner loop of mergeonerun().
@@ -1563,11 +1576,11 @@ tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
int tupIndex;
SortTuple *newtup;
- *stup = state->memtuples[0];
+ *ptup = state->memtuples[0];
/* returned tuple is no longer counted in our memory space */
- if (stup->tuple)
+ if (ptup->tuple)
{
- tuplen = GetMemoryChunkSpace(stup->tuple);
+ tuplen = GetMemoryChunkSpace(ptup->tuple);
state->availMem += tuplen;
state->mergeavailmem[srcTape] += tuplen;
}
@@ -1598,6 +1611,8 @@ tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
newtup->tupindex = state->mergefreelist;
state->mergefreelist = tupIndex;
state->mergeavailslots[srcTape]++;
+ if (!stup && dummy.tuple)
+ pfree(dummy.tuple);
return true;
}
return false;
@@ -1620,20 +1635,22 @@ tuplesort_gettupleslot(Tuplesortstate *state, bool forward,
MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext);
SortTuple stup;
bool should_free;
+ bool found;
- if (!tuplesort_gettuple_common(state, forward, &stup, &should_free))
- stup.tuple = NULL;
+ found = tuplesort_gettuple_common(state, forward, (slot ? &stup : NULL), &should_free);
MemoryContextSwitchTo(oldcontext);
- if (stup.tuple)
+ if (found)
{
- ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free);
+ if (slot)
+ ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free);
return true;
}
else
{
- ExecClearTuple(slot);
+ if (slot)
+ ExecClearTuple(slot);
return false;
}
}
@@ -1692,24 +1709,27 @@ tuplesort_getdatum(Tuplesortstate *state, bool forward,
SortTuple stup;
bool should_free;
- if (!tuplesort_gettuple_common(state, forward, &stup, &should_free))
+ if (!tuplesort_gettuple_common(state, forward, (val ? &stup : NULL), &should_free))
{
MemoryContextSwitchTo(oldcontext);
return false;
}
- if (stup.isnull1 || state->datumTypeByVal)
+ if (val)
{
- *val = stup.datum1;
- *isNull = stup.isnull1;
- }
- else
- {
- if (should_free)
+ if (stup.isnull1 || state->datumTypeByVal)
+ {
*val = stup.datum1;
+ *isNull = stup.isnull1;
+ }
else
- *val = datumCopy(stup.datum1, false, state->datumTypeLen);
- *isNull = false;
+ {
+ if (should_free)
+ *val = stup.datum1;
+ else
+ *val = datumCopy(stup.datum1, false, state->datumTypeLen);
+ *isNull = false;
+ }
}
MemoryContextSwitchTo(oldcontext);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 769058d..c70fb1a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -229,6 +229,7 @@ static void getTableData(TableInfo *tblinfo, int numTables, bool oids);
static void makeTableDataInfo(TableInfo *tbinfo, bool oids);
static void buildMatViewRefreshDependencies(Archive *fout);
static void getTableDataFKConstraints(void);
+static char *format_aggregate_arguments(FuncInfo *finfo, char *funcargs);
static char *format_function_arguments(FuncInfo *finfo, char *funcargs,
bool is_agg);
static char *format_function_arguments_old(Archive *fout,
@@ -9439,6 +9440,22 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
}
/*
+ * format_aggregate_arguments: generate function name and argument list
+ *
+ * This is used when we can rely on pg_get_aggregate_arguments to format
+ * the argument list.
+ */
+static char *
+format_aggregate_arguments(FuncInfo *finfo, char *funcargs)
+{
+ PQExpBufferData fn;
+
+ initPQExpBuffer(&fn);
+ appendPQExpBuffer(&fn, "%s%s", fmtId(finfo->dobj.name), funcargs);
+ return fn.data;
+}
+
+/*
* format_function_arguments: generate function name and argument list
*
* This is used when we can rely on pg_get_function_arguments to format
@@ -11493,15 +11510,22 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
int i_aggtransfn;
int i_aggfinalfn;
int i_aggsortop;
+ int i_aggtranssortop;
+ int i_hypothetical;
+ int i_isstrict;
int i_aggtranstype;
int i_agginitval;
int i_convertok;
const char *aggtransfn;
const char *aggfinalfn;
const char *aggsortop;
+ const char *aggtranssortop;
const char *aggtranstype;
const char *agginitval;
+ bool hypothetical;
+ bool isstrict;
bool convertok;
+ bool has_comma = false;
/* Skip if not to be dumped */
if (!agginfo->aggfn.dobj.dump || dataOnly)
@@ -11517,11 +11541,31 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
selectSourceSchema(fout, agginfo->aggfn.dobj.namespace->dobj.name);
/* Get aggregate-specific details */
- if (fout->remoteVersion >= 80400)
+ if (fout->remoteVersion >= 90400)
{
appendPQExpBuffer(query, "SELECT aggtransfn, "
"aggfinalfn, aggtranstype::pg_catalog.regtype, "
"aggsortop::pg_catalog.regoperator, "
+ "aggtranssortop::pg_catalog.regoperator, "
+ "(aggordnargs = -2) as hypothetical, "
+ "p.proisstrict as isstrict, "
+ "agginitval, "
+ "'t'::boolean AS convertok, "
+ "pg_catalog.pg_get_aggregate_arguments(p.oid) AS funcargs, "
+ "pg_catalog.pg_get_aggregate_identity_arguments(p.oid) AS funciargs "
+ "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
+ "WHERE a.aggfnoid = p.oid "
+ "AND p.oid = '%u'::pg_catalog.oid",
+ agginfo->aggfn.dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 80400)
+ {
+ appendPQExpBuffer(query, "SELECT aggtransfn, "
+ "aggfinalfn, aggtranstype::pg_catalog.regtype, "
+ "aggsortop::pg_catalog.regoperator, "
+ "0 as aggtranssortop, "
+ "false as hypothetical, "
+ "false as isstrict, "
"agginitval, "
"'t'::boolean AS convertok, "
"pg_catalog.pg_get_function_arguments(p.oid) AS funcargs, "
@@ -11536,6 +11580,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
appendPQExpBuffer(query, "SELECT aggtransfn, "
"aggfinalfn, aggtranstype::pg_catalog.regtype, "
"aggsortop::pg_catalog.regoperator, "
+ "0 as aggtranssortop, "
+ "false as hypothetical, "
+ "false as isstrict, "
"agginitval, "
"'t'::boolean AS convertok "
"FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
@@ -11548,6 +11595,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
appendPQExpBuffer(query, "SELECT aggtransfn, "
"aggfinalfn, aggtranstype::pg_catalog.regtype, "
"0 AS aggsortop, "
+ "0 as aggtranssortop, "
+ "'f'::boolean as hypothetical, "
+ "'f'::boolean as isstrict, "
"agginitval, "
"'t'::boolean AS convertok "
"FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
@@ -11560,6 +11610,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
appendPQExpBuffer(query, "SELECT aggtransfn, aggfinalfn, "
"format_type(aggtranstype, NULL) AS aggtranstype, "
"0 AS aggsortop, "
+ "0 as aggtranssortop, "
+ "'f'::boolean as hypothetical, "
+ "'f'::boolean as isstrict, "
"agginitval, "
"'t'::boolean AS convertok "
"FROM pg_aggregate "
@@ -11572,6 +11625,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
"aggfinalfn, "
"(SELECT typname FROM pg_type WHERE oid = aggtranstype1) AS aggtranstype, "
"0 AS aggsortop, "
+ "0 as aggtranssortop, "
+ "'f'::boolean as hypothetical, "
+ "'f'::boolean as isstrict, "
"agginitval1 AS agginitval, "
"(aggtransfn2 = 0 and aggtranstype2 = 0 and agginitval2 is null) AS convertok "
"FROM pg_aggregate "
@@ -11584,6 +11640,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
i_aggtransfn = PQfnumber(res, "aggtransfn");
i_aggfinalfn = PQfnumber(res, "aggfinalfn");
i_aggsortop = PQfnumber(res, "aggsortop");
+ i_aggtranssortop = PQfnumber(res, "aggtranssortop");
+ i_hypothetical = PQfnumber(res, "hypothetical");
+ i_isstrict = PQfnumber(res, "isstrict");
i_aggtranstype = PQfnumber(res, "aggtranstype");
i_agginitval = PQfnumber(res, "agginitval");
i_convertok = PQfnumber(res, "convertok");
@@ -11591,11 +11650,25 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
aggsortop = PQgetvalue(res, 0, i_aggsortop);
+ aggtranssortop = PQgetvalue(res, 0, i_aggtranssortop);
+ hypothetical = (PQgetvalue(res, 0, i_hypothetical)[0] == 't');
+ isstrict = (PQgetvalue(res, 0, i_isstrict)[0] == 't');
aggtranstype = PQgetvalue(res, 0, i_aggtranstype);
agginitval = PQgetvalue(res, 0, i_agginitval);
convertok = (PQgetvalue(res, 0, i_convertok)[0] == 't');
- if (fout->remoteVersion >= 80400)
+ if (fout->remoteVersion >= 90400)
+ {
+ /* 9.4 or later; we rely on server-side code for almost all of the work */
+ char *funcargs;
+ char *funciargs;
+
+ funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs"));
+ funciargs = PQgetvalue(res, 0, PQfnumber(res, "funciargs"));
+ aggfullsig = format_aggregate_arguments(&agginfo->aggfn, funcargs);
+ aggsig = format_aggregate_arguments(&agginfo->aggfn, funciargs);
+ }
+ else if (fout->remoteVersion >= 80400)
{
/* 8.4 or later; we rely on server-side code for most of the work */
char *funcargs;
@@ -11625,36 +11698,58 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
if (fout->remoteVersion >= 70300)
{
/* If using 7.3's regproc or regtype, data is already quoted */
- appendPQExpBuffer(details, " SFUNC = %s,\n STYPE = %s",
- aggtransfn,
- aggtranstype);
+ /*
+ * either or both of SFUNC and STYPE might be missing in >90400,
+ * but if SFUNC is missing, then FINALFUNC will always be present,
+ * and if SFUNC is present then STYPE must also be present; the
+ * code below relies on these conditions to keep the commas in the
+ * right places. STRICT must be forced to false if SFUNC is present.
+ */
+
+ if (strcmp(aggtransfn,"-") != 0)
+ {
+ appendPQExpBuffer(details, "\n SFUNC = %s,", aggtransfn);
+ isstrict = false;
+ }
+
+ if (strcmp(aggtranstype,"-") != 0)
+ appendPQExpBuffer(details, "\n STYPE = %s", aggtranstype);
+ else
+ has_comma = true;
}
else if (fout->remoteVersion >= 70100)
{
/* format_type quotes, regproc does not */
- appendPQExpBuffer(details, " SFUNC = %s,\n STYPE = %s",
+ appendPQExpBuffer(details, "\n SFUNC = %s,\n STYPE = %s",
fmtId(aggtransfn),
aggtranstype);
}
else
{
/* need quotes all around */
- appendPQExpBuffer(details, " SFUNC = %s,\n",
+ appendPQExpBuffer(details, "\n SFUNC = %s,\n",
fmtId(aggtransfn));
appendPQExpBuffer(details, " STYPE = %s",
fmtId(aggtranstype));
}
- if (!PQgetisnull(res, 0, i_agginitval))
+ if (strcmp(aggfinalfn, "-") != 0)
{
- appendPQExpBuffer(details, ",\n INITCOND = ");
- appendStringLiteralAH(details, agginitval, fout);
+ appendPQExpBuffer(details, "%s\n FINALFUNC = %s",
+ (has_comma ? "" : ","),
+ aggfinalfn);
}
- if (strcmp(aggfinalfn, "-") != 0)
+ if (hypothetical)
+ appendPQExpBuffer(details, ",\n HYPOTHETICAL");
+
+ if (isstrict)
+ appendPQExpBuffer(details, ",\n STRICT");
+
+ if (!PQgetisnull(res, 0, i_agginitval))
{
- appendPQExpBuffer(details, ",\n FINALFUNC = %s",
- aggfinalfn);
+ appendPQExpBuffer(details, ",\n INITCOND = ");
+ appendStringLiteralAH(details, agginitval, fout);
}
aggsortop = convertOperatorReference(fout, aggsortop);
@@ -11664,6 +11759,13 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
aggsortop);
}
+ aggtranssortop = convertOperatorReference(fout, aggtranssortop);
+ if (aggtranssortop)
+ {
+ appendPQExpBuffer(details, ",\n TRANSSORTOP = %s",
+ aggtranssortop);
+ }
+
/*
* DROP must be fully qualified in case same name appears in pg_catalog
*/
@@ -11671,7 +11773,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
fmtId(agginfo->aggfn.dobj.namespace->dobj.name),
aggsig);
- appendPQExpBuffer(q, "CREATE AGGREGATE %s (\n%s\n);\n",
+ appendPQExpBuffer(q, "CREATE AGGREGATE %s (%s\n);\n",
aggfullsig, details->data);
appendPQExpBuffer(labelq, "AGGREGATE %s", aggsig);
@@ -11700,7 +11802,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
/*
* Since there is no GRANT ON AGGREGATE syntax, we have to make the ACL
* command look like a function's GRANT; in particular this affects the
- * syntax for zero-argument aggregates.
+ * syntax for zero-argument aggregates and ordered set functions.
*/
free(aggsig);
free(aggsig_tag);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ceda13e..df6be07 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -72,7 +72,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Name"),
gettext_noop("Result data type"));
- if (pset.sversion >= 80400)
+ if (pset.sversion >= 90400)
+ appendPQExpBuffer(&buf,
+ " pg_catalog.pg_get_aggregate_arguments(p.oid) AS \"%s\",\n",
+ gettext_noop("Argument data types"));
+ else if (pset.sversion >= 80400)
appendPQExpBuffer(&buf,
" CASE WHEN p.pronargs = 0\n"
" THEN CAST('*' AS pg_catalog.text)\n"
@@ -254,7 +258,26 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
gettext_noop("Schema"),
gettext_noop("Name"));
- if (pset.sversion >= 80400)
+ if (pset.sversion >= 90400)
+ appendPQExpBuffer(&buf,
+ " pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n"
+ " CASE WHEN p.proisagg THEN pg_catalog.pg_get_aggregate_arguments(p.oid)\n"
+ " ELSE pg_catalog.pg_get_function_arguments(p.oid) END as \"%s\",\n"
+ " CASE\n"
+ " WHEN p.proisagg THEN '%s'\n"
+ " WHEN p.proiswindow THEN '%s'\n"
+ " WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN '%s'\n"
+ " ELSE '%s'\n"
+ " END as \"%s\"",
+ gettext_noop("Result data type"),
+ gettext_noop("Argument data types"),
+ /* translator: "agg" is short for "aggregate" */
+ gettext_noop("agg"),
+ gettext_noop("window"),
+ gettext_noop("trigger"),
+ gettext_noop("normal"),
+ gettext_noop("Type"));
+ else if (pset.sversion >= 80400)
appendPQExpBuffer(&buf,
" pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n"
" pg_catalog.pg_get_function_arguments(p.oid) as \"%s\",\n"
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 5ad6ea6..b546ca4 100644
--- a/src/include/catalog/pg_aggregate.h
+++ b/src/include/catalog/pg_aggregate.h
@@ -32,6 +32,9 @@
* aggfinalfn final function (0 if none)
* aggsortop associated sort operator (0 if none)
* aggtranstype type of aggregate's transition (state) data
+ * aggtranssortop An optional sort operator for the type aggtranstype
+ * aggordnargs Number of direct arguments to aggregate.
+ * aggisordsetfunc A flag to represent whether a function is ordered set or not
* agginitval initial value for transition state (can be NULL)
* ----------------------------------------------------------------
*/
@@ -44,6 +47,9 @@ CATALOG(pg_aggregate,2600) BKI_WITHOUT_OIDS
regproc aggfinalfn;
Oid aggsortop;
Oid aggtranstype;
+ Oid aggtranssortop;
+ int32 aggordnargs;
+ bool aggisordsetfunc;
#ifdef CATALOG_VARLEN /* variable-length fields start here */
text agginitval;
@@ -62,13 +68,16 @@ typedef FormData_pg_aggregate *Form_pg_aggregate;
* ----------------
*/
-#define Natts_pg_aggregate 6
+#define Natts_pg_aggregate 9
#define Anum_pg_aggregate_aggfnoid 1
#define Anum_pg_aggregate_aggtransfn 2
#define Anum_pg_aggregate_aggfinalfn 3
#define Anum_pg_aggregate_aggsortop 4
#define Anum_pg_aggregate_aggtranstype 5
-#define Anum_pg_aggregate_agginitval 6
+#define Anum_pg_aggregate_aggtranssortop 6
+#define Anum_pg_aggregate_aggordnargs 7
+#define Anum_pg_aggregate_aggisordsetfunc 8
+#define Anum_pg_aggregate_agginitval 9
/* ----------------
@@ -77,163 +86,176 @@ typedef FormData_pg_aggregate *Form_pg_aggregate;
*/
/* avg */
-DATA(insert ( 2100 int8_avg_accum numeric_avg 0 1231 "{0,0}" ));
-DATA(insert ( 2101 int4_avg_accum int8_avg 0 1016 "{0,0}" ));
-DATA(insert ( 2102 int2_avg_accum int8_avg 0 1016 "{0,0}" ));
-DATA(insert ( 2103 numeric_avg_accum numeric_avg 0 1231 "{0,0}" ));
-DATA(insert ( 2104 float4_accum float8_avg 0 1022 "{0,0,0}" ));
-DATA(insert ( 2105 float8_accum float8_avg 0 1022 "{0,0,0}" ));
-DATA(insert ( 2106 interval_accum interval_avg 0 1187 "{0 second,0 second}" ));
+DATA(insert ( 2100 int8_avg_accum numeric_avg 0 1231 0 -1 f "{0,0}" ));
+DATA(insert ( 2101 int4_avg_accum int8_avg 0 1016 0 -1 f "{0,0}" ));
+DATA(insert ( 2102 int2_avg_accum int8_avg 0 1016 0 -1 f "{0,0}" ));
+DATA(insert ( 2103 numeric_avg_accum numeric_avg 0 1231 0 -1 f "{0,0}" ));
+DATA(insert ( 2104 float4_accum float8_avg 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2105 float8_accum float8_avg 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2106 interval_accum interval_avg 0 1187 0 -1 f "{0 second,0 second}" ));
/* sum */
-DATA(insert ( 2107 int8_sum - 0 1700 _null_ ));
-DATA(insert ( 2108 int4_sum - 0 20 _null_ ));
-DATA(insert ( 2109 int2_sum - 0 20 _null_ ));
-DATA(insert ( 2110 float4pl - 0 700 _null_ ));
-DATA(insert ( 2111 float8pl - 0 701 _null_ ));
-DATA(insert ( 2112 cash_pl - 0 790 _null_ ));
-DATA(insert ( 2113 interval_pl - 0 1186 _null_ ));
-DATA(insert ( 2114 numeric_add - 0 1700 _null_ ));
+DATA(insert ( 2107 int8_sum - 0 1700 0 -1 f _null_ ));
+DATA(insert ( 2108 int4_sum - 0 20 0 -1 f _null_ ));
+DATA(insert ( 2109 int2_sum - 0 20 0 -1 f _null_ ));
+DATA(insert ( 2110 float4pl - 0 700 0 -1 f _null_ ));
+DATA(insert ( 2111 float8pl - 0 701 0 -1 f _null_ ));
+DATA(insert ( 2112 cash_pl - 0 790 0 -1 f _null_ ));
+DATA(insert ( 2113 interval_pl - 0 1186 0 -1 f _null_ ));
+DATA(insert ( 2114 numeric_add - 0 1700 0 -1 f _null_ ));
/* max */
-DATA(insert ( 2115 int8larger - 413 20 _null_ ));
-DATA(insert ( 2116 int4larger - 521 23 _null_ ));
-DATA(insert ( 2117 int2larger - 520 21 _null_ ));
-DATA(insert ( 2118 oidlarger - 610 26 _null_ ));
-DATA(insert ( 2119 float4larger - 623 700 _null_ ));
-DATA(insert ( 2120 float8larger - 674 701 _null_ ));
-DATA(insert ( 2121 int4larger - 563 702 _null_ ));
-DATA(insert ( 2122 date_larger - 1097 1082 _null_ ));
-DATA(insert ( 2123 time_larger - 1112 1083 _null_ ));
-DATA(insert ( 2124 timetz_larger - 1554 1266 _null_ ));
-DATA(insert ( 2125 cashlarger - 903 790 _null_ ));
-DATA(insert ( 2126 timestamp_larger - 2064 1114 _null_ ));
-DATA(insert ( 2127 timestamptz_larger - 1324 1184 _null_ ));
-DATA(insert ( 2128 interval_larger - 1334 1186 _null_ ));
-DATA(insert ( 2129 text_larger - 666 25 _null_ ));
-DATA(insert ( 2130 numeric_larger - 1756 1700 _null_ ));
-DATA(insert ( 2050 array_larger - 1073 2277 _null_ ));
-DATA(insert ( 2244 bpchar_larger - 1060 1042 _null_ ));
-DATA(insert ( 2797 tidlarger - 2800 27 _null_ ));
-DATA(insert ( 3526 enum_larger - 3519 3500 _null_ ));
+DATA(insert ( 2115 int8larger - 413 20 0 -1 f _null_ ));
+DATA(insert ( 2116 int4larger - 521 23 0 -1 f _null_ ));
+DATA(insert ( 2117 int2larger - 520 21 0 -1 f _null_ ));
+DATA(insert ( 2118 oidlarger - 610 26 0 -1 f _null_ ));
+DATA(insert ( 2119 float4larger - 623 700 0 -1 f _null_ ));
+DATA(insert ( 2120 float8larger - 674 701 0 -1 f _null_ ));
+DATA(insert ( 2121 int4larger - 563 702 0 -1 f _null_ ));
+DATA(insert ( 2122 date_larger - 1097 1082 0 -1 f _null_ ));
+DATA(insert ( 2123 time_larger - 1112 1083 0 -1 f _null_ ));
+DATA(insert ( 2124 timetz_larger - 1554 1266 0 -1 f _null_ ));
+DATA(insert ( 2125 cashlarger - 903 790 0 -1 f _null_ ));
+DATA(insert ( 2126 timestamp_larger - 2064 1114 0 -1 f _null_ ));
+DATA(insert ( 2127 timestamptz_larger - 1324 1184 0 -1 f _null_ ));
+DATA(insert ( 2128 interval_larger - 1334 1186 0 -1 f _null_ ));
+DATA(insert ( 2129 text_larger - 666 25 0 -1 f _null_ ));
+DATA(insert ( 2130 numeric_larger - 1756 1700 0 -1 f _null_ ));
+DATA(insert ( 2050 array_larger - 1073 2277 0 -1 f _null_ ));
+DATA(insert ( 2244 bpchar_larger - 1060 1042 0 -1 f _null_ ));
+DATA(insert ( 2797 tidlarger - 2800 27 0 -1 f _null_ ));
+DATA(insert ( 3526 enum_larger - 3519 3500 0 -1 f _null_ ));
/* min */
-DATA(insert ( 2131 int8smaller - 412 20 _null_ ));
-DATA(insert ( 2132 int4smaller - 97 23 _null_ ));
-DATA(insert ( 2133 int2smaller - 95 21 _null_ ));
-DATA(insert ( 2134 oidsmaller - 609 26 _null_ ));
-DATA(insert ( 2135 float4smaller - 622 700 _null_ ));
-DATA(insert ( 2136 float8smaller - 672 701 _null_ ));
-DATA(insert ( 2137 int4smaller - 562 702 _null_ ));
-DATA(insert ( 2138 date_smaller - 1095 1082 _null_ ));
-DATA(insert ( 2139 time_smaller - 1110 1083 _null_ ));
-DATA(insert ( 2140 timetz_smaller - 1552 1266 _null_ ));
-DATA(insert ( 2141 cashsmaller - 902 790 _null_ ));
-DATA(insert ( 2142 timestamp_smaller - 2062 1114 _null_ ));
-DATA(insert ( 2143 timestamptz_smaller - 1322 1184 _null_ ));
-DATA(insert ( 2144 interval_smaller - 1332 1186 _null_ ));
-DATA(insert ( 2145 text_smaller - 664 25 _null_ ));
-DATA(insert ( 2146 numeric_smaller - 1754 1700 _null_ ));
-DATA(insert ( 2051 array_smaller - 1072 2277 _null_ ));
-DATA(insert ( 2245 bpchar_smaller - 1058 1042 _null_ ));
-DATA(insert ( 2798 tidsmaller - 2799 27 _null_ ));
-DATA(insert ( 3527 enum_smaller - 3518 3500 _null_ ));
+DATA(insert ( 2131 int8smaller - 412 20 0 -1 f _null_ ));
+DATA(insert ( 2132 int4smaller - 97 23 0 -1 f _null_ ));
+DATA(insert ( 2133 int2smaller - 95 21 0 -1 f _null_ ));
+DATA(insert ( 2134 oidsmaller - 609 26 0 -1 f _null_ ));
+DATA(insert ( 2135 float4smaller - 622 700 0 -1 f _null_ ));
+DATA(insert ( 2136 float8smaller - 672 701 0 -1 f _null_ ));
+DATA(insert ( 2137 int4smaller - 562 702 0 -1 f _null_ ));
+DATA(insert ( 2138 date_smaller - 1095 1082 0 -1 f _null_ ));
+DATA(insert ( 2139 time_smaller - 1110 1083 0 -1 f _null_ ));
+DATA(insert ( 2140 timetz_smaller - 1552 1266 0 -1 f _null_ ));
+DATA(insert ( 2141 cashsmaller - 902 790 0 -1 f _null_ ));
+DATA(insert ( 2142 timestamp_smaller - 2062 1114 0 -1 f _null_ ));
+DATA(insert ( 2143 timestamptz_smaller - 1322 1184 0 -1 f _null_ ));
+DATA(insert ( 2144 interval_smaller - 1332 1186 0 -1 f _null_ ));
+DATA(insert ( 2145 text_smaller - 664 25 0 -1 f _null_ ));
+DATA(insert ( 2146 numeric_smaller - 1754 1700 0 -1 f _null_ ));
+DATA(insert ( 2051 array_smaller - 1072 2277 0 -1 f _null_ ));
+DATA(insert ( 2245 bpchar_smaller - 1058 1042 0 -1 f _null_ ));
+DATA(insert ( 2798 tidsmaller - 2799 27 0 -1 f _null_ ));
+DATA(insert ( 3527 enum_smaller - 3518 3500 0 -1 f _null_ ));
/* count */
-DATA(insert ( 2147 int8inc_any - 0 20 "0" ));
-DATA(insert ( 2803 int8inc - 0 20 "0" ));
+DATA(insert ( 2147 int8inc_any - 0 20 0 -1 f "0" ));
+DATA(insert ( 2803 int8inc - 0 20 0 -1 f "0" ));
/* var_pop */
-DATA(insert ( 2718 int8_accum numeric_var_pop 0 1231 "{0,0,0}" ));
-DATA(insert ( 2719 int4_accum numeric_var_pop 0 1231 "{0,0,0}" ));
-DATA(insert ( 2720 int2_accum numeric_var_pop 0 1231 "{0,0,0}" ));
-DATA(insert ( 2721 float4_accum float8_var_pop 0 1022 "{0,0,0}" ));
-DATA(insert ( 2722 float8_accum float8_var_pop 0 1022 "{0,0,0}" ));
-DATA(insert ( 2723 numeric_accum numeric_var_pop 0 1231 "{0,0,0}" ));
+DATA(insert ( 2718 int8_accum numeric_var_pop 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2719 int4_accum numeric_var_pop 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2720 int2_accum numeric_var_pop 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2721 float4_accum float8_var_pop 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2722 float8_accum float8_var_pop 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2723 numeric_accum numeric_var_pop 0 1231 0 -1 f "{0,0,0}" ));
/* var_samp */
-DATA(insert ( 2641 int8_accum numeric_var_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2642 int4_accum numeric_var_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2643 int2_accum numeric_var_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2644 float4_accum float8_var_samp 0 1022 "{0,0,0}" ));
-DATA(insert ( 2645 float8_accum float8_var_samp 0 1022 "{0,0,0}" ));
-DATA(insert ( 2646 numeric_accum numeric_var_samp 0 1231 "{0,0,0}" ));
+DATA(insert ( 2641 int8_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2642 int4_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2643 int2_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2644 float4_accum float8_var_samp 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2645 float8_accum float8_var_samp 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2646 numeric_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" ));
/* variance: historical Postgres syntax for var_samp */
-DATA(insert ( 2148 int8_accum numeric_var_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2149 int4_accum numeric_var_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2150 int2_accum numeric_var_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2151 float4_accum float8_var_samp 0 1022 "{0,0,0}" ));
-DATA(insert ( 2152 float8_accum float8_var_samp 0 1022 "{0,0,0}" ));
-DATA(insert ( 2153 numeric_accum numeric_var_samp 0 1231 "{0,0,0}" ));
+DATA(insert ( 2148 int8_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2149 int4_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2150 int2_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2151 float4_accum float8_var_samp 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2152 float8_accum float8_var_samp 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2153 numeric_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" ));
/* stddev_pop */
-DATA(insert ( 2724 int8_accum numeric_stddev_pop 0 1231 "{0,0,0}" ));
-DATA(insert ( 2725 int4_accum numeric_stddev_pop 0 1231 "{0,0,0}" ));
-DATA(insert ( 2726 int2_accum numeric_stddev_pop 0 1231 "{0,0,0}" ));
-DATA(insert ( 2727 float4_accum float8_stddev_pop 0 1022 "{0,0,0}" ));
-DATA(insert ( 2728 float8_accum float8_stddev_pop 0 1022 "{0,0,0}" ));
-DATA(insert ( 2729 numeric_accum numeric_stddev_pop 0 1231 "{0,0,0}" ));
+DATA(insert ( 2724 int8_accum numeric_stddev_pop 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2725 int4_accum numeric_stddev_pop 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2726 int2_accum numeric_stddev_pop 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2727 float4_accum float8_stddev_pop 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2728 float8_accum float8_stddev_pop 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2729 numeric_accum numeric_stddev_pop 0 1231 0 -1 f "{0,0,0}" ));
/* stddev_samp */
-DATA(insert ( 2712 int8_accum numeric_stddev_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2713 int4_accum numeric_stddev_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2714 int2_accum numeric_stddev_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2715 float4_accum float8_stddev_samp 0 1022 "{0,0,0}" ));
-DATA(insert ( 2716 float8_accum float8_stddev_samp 0 1022 "{0,0,0}" ));
-DATA(insert ( 2717 numeric_accum numeric_stddev_samp 0 1231 "{0,0,0}" ));
+DATA(insert ( 2712 int8_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2713 int4_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2714 int2_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2715 float4_accum float8_stddev_samp 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2716 float8_accum float8_stddev_samp 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2717 numeric_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" ));
/* stddev: historical Postgres syntax for stddev_samp */
-DATA(insert ( 2154 int8_accum numeric_stddev_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2155 int4_accum numeric_stddev_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2156 int2_accum numeric_stddev_samp 0 1231 "{0,0,0}" ));
-DATA(insert ( 2157 float4_accum float8_stddev_samp 0 1022 "{0,0,0}" ));
-DATA(insert ( 2158 float8_accum float8_stddev_samp 0 1022 "{0,0,0}" ));
-DATA(insert ( 2159 numeric_accum numeric_stddev_samp 0 1231 "{0,0,0}" ));
+DATA(insert ( 2154 int8_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2155 int4_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2156 int2_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2157 float4_accum float8_stddev_samp 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2158 float8_accum float8_stddev_samp 0 1022 0 -1 f "{0,0,0}" ));
+DATA(insert ( 2159 numeric_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" ));
/* SQL2003 binary regression aggregates */
-DATA(insert ( 2818 int8inc_float8_float8 - 0 20 "0" ));
-DATA(insert ( 2819 float8_regr_accum float8_regr_sxx 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2820 float8_regr_accum float8_regr_syy 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2821 float8_regr_accum float8_regr_sxy 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2822 float8_regr_accum float8_regr_avgx 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2823 float8_regr_accum float8_regr_avgy 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2824 float8_regr_accum float8_regr_r2 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2825 float8_regr_accum float8_regr_slope 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2826 float8_regr_accum float8_regr_intercept 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2827 float8_regr_accum float8_covar_pop 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2828 float8_regr_accum float8_covar_samp 0 1022 "{0,0,0,0,0,0}" ));
-DATA(insert ( 2829 float8_regr_accum float8_corr 0 1022 "{0,0,0,0,0,0}" ));
+DATA(insert ( 2818 int8inc_float8_float8 - 0 20 0 -1 f "0" ));
+DATA(insert ( 2819 float8_regr_accum float8_regr_sxx 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2820 float8_regr_accum float8_regr_syy 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2821 float8_regr_accum float8_regr_sxy 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2822 float8_regr_accum float8_regr_avgx 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2823 float8_regr_accum float8_regr_avgy 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2824 float8_regr_accum float8_regr_r2 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2825 float8_regr_accum float8_regr_slope 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2826 float8_regr_accum float8_regr_intercept 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2827 float8_regr_accum float8_covar_pop 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2828 float8_regr_accum float8_covar_samp 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
+DATA(insert ( 2829 float8_regr_accum float8_corr 0 1022 0 -1 f "{0,0,0,0,0,0}" ));
/* boolean-and and boolean-or */
-DATA(insert ( 2517 booland_statefunc - 58 16 _null_ ));
-DATA(insert ( 2518 boolor_statefunc - 59 16 _null_ ));
-DATA(insert ( 2519 booland_statefunc - 58 16 _null_ ));
+DATA(insert ( 2517 booland_statefunc - 58 16 0 -1 f _null_ ));
+DATA(insert ( 2518 boolor_statefunc - 59 16 0 -1 f _null_ ));
+DATA(insert ( 2519 booland_statefunc - 58 16 0 -1 f _null_ ));
/* bitwise integer */
-DATA(insert ( 2236 int2and - 0 21 _null_ ));
-DATA(insert ( 2237 int2or - 0 21 _null_ ));
-DATA(insert ( 2238 int4and - 0 23 _null_ ));
-DATA(insert ( 2239 int4or - 0 23 _null_ ));
-DATA(insert ( 2240 int8and - 0 20 _null_ ));
-DATA(insert ( 2241 int8or - 0 20 _null_ ));
-DATA(insert ( 2242 bitand - 0 1560 _null_ ));
-DATA(insert ( 2243 bitor - 0 1560 _null_ ));
+DATA(insert ( 2236 int2and - 0 21 0 -1 f _null_ ));
+DATA(insert ( 2237 int2or - 0 21 0 -1 f _null_ ));
+DATA(insert ( 2238 int4and - 0 23 0 -1 f _null_ ));
+DATA(insert ( 2239 int4or - 0 23 0 -1 f _null_ ));
+DATA(insert ( 2240 int8and - 0 20 0 -1 f _null_ ));
+DATA(insert ( 2241 int8or - 0 20 0 -1 f _null_ ));
+DATA(insert ( 2242 bitand - 0 1560 0 -1 f _null_ ));
+DATA(insert ( 2243 bitor - 0 1560 0 -1 f _null_ ));
/* xml */
-DATA(insert ( 2901 xmlconcat2 - 0 142 _null_ ));
+DATA(insert ( 2901 xmlconcat2 - 0 142 0 -1 f _null_ ));
/* array */
-DATA(insert ( 2335 array_agg_transfn array_agg_finalfn 0 2281 _null_ ));
+DATA(insert ( 2335 array_agg_transfn array_agg_finalfn 0 2281 0 -1 f _null_ ));
/* text */
-DATA(insert ( 3538 string_agg_transfn string_agg_finalfn 0 2281 _null_ ));
+DATA(insert ( 3538 string_agg_transfn string_agg_finalfn 0 2281 0 -1 f _null_ ));
/* bytea */
-DATA(insert ( 3545 bytea_string_agg_transfn bytea_string_agg_finalfn 0 2281 _null_ ));
+DATA(insert ( 3545 bytea_string_agg_transfn bytea_string_agg_finalfn 0 2281 0 -1 f _null_ ));
/* json */
-DATA(insert ( 3175 json_agg_transfn json_agg_finalfn 0 2281 _null_ ));
+DATA(insert ( 3175 json_agg_transfn json_agg_finalfn 0 2281 0 -1 f _null_ ));
+
+/* ordered set functions */
+DATA(insert ( 3931 - percentile_disc_final 0 0 0 1 t _null_));
+DATA(insert ( 3935 - percentile_cont_float8_final 0 0 0 1 t _null_));
+DATA(insert ( 3939 - percentile_cont_interval_final 0 0 0 1 t _null_));
+DATA(insert ( 3920 - rank_final 0 16 59 -2 t "f"));
+DATA(insert ( 3970 - dense_rank_final 0 16 59 -2 t "f"));
+DATA(insert ( 3972 - percent_rank_final 0 16 59 -2 t "f"));
+DATA(insert ( 3974 - cume_dist_final 0 16 58 -2 t "f"));
+DATA(insert ( 3976 - mode_final 0 0 0 0 t _null_));
+DATA(insert ( 3978 - percentile_disc_multi_final 0 0 0 1 t _null_));
+DATA(insert ( 3980 - percentile_cont_float8_multi_final 0 0 0 1 t _null_));
+DATA(insert ( 3982 - percentile_cont_interval_multi_final 0 0 0 1 t _null_));
/*
* prototypes for functions in pg_aggregate.c
@@ -241,6 +263,7 @@ DATA(insert ( 3175 json_agg_transfn json_agg_finalfn 0 2281 _null_ ));
extern Oid AggregateCreate(const char *aggName,
Oid aggNamespace,
int numArgs,
+ int numDirectArgs,
oidvector *parameterTypes,
Datum allParameterTypes,
Datum parameterModes,
@@ -249,7 +272,11 @@ extern Oid AggregateCreate(const char *aggName,
List *aggtransfnName,
List *aggfinalfnName,
List *aggsortopName,
+ List *aggtranssortopName,
Oid aggTransType,
- const char *agginitval);
+ const char *agginitval,
+ bool isStrict,
+ bool isOrderedSetFunc,
+ bool isHypotheticalSet);
#endif /* PG_AGGREGATE_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ca4fc62..a0d2e12 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1973,6 +1973,10 @@ DATA(insert OID = 2232 ( pg_get_function_identity_arguments PGNSP PGUID 12 1
DESCR("identity argument list of a function");
DATA(insert OID = 2165 ( pg_get_function_result PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ pg_get_function_result _null_ _null_ _null_ ));
DESCR("result type of a function");
+DATA(insert OID = 3178 ( pg_get_aggregate_arguments PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ pg_get_aggregate_arguments _null_ _null_ _null_ ));
+DESCR("argument list of an aggregate function");
+DATA(insert OID = 3179 ( pg_get_aggregate_identity_arguments PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ pg_get_aggregate_identity_arguments _null_ _null_ _null_ ));
+DESCR("identity argument list of an aggregate function");
DATA(insert OID = 1686 ( pg_get_keywords PGNSP PGUID 12 10 400 0 0 f f f f t t s 0 0 2249 "" "{25,18,25}" "{o,o,o}" "{word,catcode,catdesc}" _null_ pg_get_keywords _null_ _null_ _null_ ));
DESCR("list of SQL keywords");
@@ -4750,6 +4754,74 @@ DESCR("SP-GiST support for quad tree over range");
/* event triggers */
DATA(insert OID = 3566 ( pg_event_trigger_dropped_objects PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25}" "{o,o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, schema_name, object_name, object_identity}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
DESCR("list objects dropped by the current command");
+
+/* inverse distribution functions */
+DATA(insert OID = 3931 ( percentile_disc PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 2283 "701 2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("discrete percentile");
+
+DATA(insert OID = 3932 ( percentile_disc_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2283 "701 2283" _null_ _null_ _null_ _null_ percentile_disc_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+DATA(insert OID = 3935 ( percentile_cont PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("continous distribution percentile for float8");
+
+DATA(insert OID = 3936 ( percentile_cont_float8_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 701 "701 701" _null_ _null_ _null_ _null_ percentile_cont_float8_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+DATA(insert OID = 3939 ( percentile_cont PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 1186 "701 1186" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("continous distribution percentile for interval");
+
+DATA(insert OID = 3940 ( percentile_cont_interval_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1186 "701 1186" _null_ _null_ _null_ _null_ percentile_cont_interval_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+/* hypothetical set functions */
+DATA(insert OID = 3920 ( rank PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("hypothetical rank");
+
+DATA(insert OID = 3969 ( rank_final PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_ hypothetical_rank_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+DATA(insert OID = 3970 ( dense_rank PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("rank of hypothetical row without gaps");
+
+DATA(insert OID = 3971 ( dense_rank_final PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_ hypothetical_dense_rank_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+DATA(insert OID = 3972 ( percent_rank PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("fractional ranking of hypothetical row within a group");
+
+DATA(insert OID = 3973 ( percent_rank_final PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_ hypothetical_percent_rank_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+DATA(insert OID = 3974 ( cume_dist PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("cumulative distribution of hypothetical row in a group");
+
+DATA(insert OID = 3975 ( cume_dist_final PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_ hypothetical_cume_dist_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+DATA(insert OID = 3976 ( mode PGNSP PGUID 12 1 0 0 0 t f f f t f i 1 0 2283 2283 _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("most common value in group");
+DATA(insert OID = 3977 ( mode_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2283 2283 _null_ _null_ _null_ _null_ mode_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+DATA(insert OID = 3978 ( percentile_disc PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 2277 "1022 2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("multiple discrete percentiles");
+
+DATA(insert OID = 3979 ( percentile_disc_multi_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2277 "1022 2283" _null_ _null_ _null_ _null_ percentile_disc_multi_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+DATA(insert OID = 3980 ( percentile_cont PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 1022 "1022 701" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("multiple continuous percentiles of float8 values");
+
+DATA(insert OID = 3981 ( percentile_cont_float8_multi_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1022 "1022 701" _null_ _null_ _null_ _null_ percentile_cont_float8_multi_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
+DATA(insert OID = 3982 ( percentile_cont PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 1187 "1022 1186" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("multiple continuous percentiles of interval values");
+
+DATA(insert OID = 3983 ( percentile_cont_interval_multi_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1187 "1022 1186" _null_ _null_ _null_ _null_ percentile_cont_interval_multi_final _null_ _null_ _null_ ));
+DESCR("ordered set final function");
+
/*
* Symbolic values for provolatile column: these indicate whether the result
* of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 1f72e1b..50ee3de 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -651,6 +651,35 @@ extern void **find_rendezvous_variable(const char *varName);
extern int AggCheckCallContext(FunctionCallInfo fcinfo,
MemoryContext *aggcontext);
+typedef struct Tuplesortstate fmTuplesortstate;
+typedef struct tupleDesc *fmTupleDesc;
+typedef struct TupleTableSlot fmTupleTableSlot;
+
+extern int64 AggSetGetRowCount(FunctionCallInfo fcinfo);
+
+extern void AggSetGetSortInfo(FunctionCallInfo fcinfo,
+ fmTuplesortstate **sortstate,
+ fmTupleDesc *tupdesc,
+ fmTupleTableSlot **tupslot,
+ Oid *datumtype);
+
+/* int16 rather than AttrNumber here to avoid includes */
+extern int AggSetGetDistinctInfo(FunctionCallInfo fcinfo,
+ fmTupleTableSlot **tupslot,
+ int16 **sortColIdx,
+ FmgrInfo **equalfns);
+
+/* int16 rather than AttrNumber here to avoid includes */
+extern int AggSetGetSortOperators(FunctionCallInfo fcinfo,
+ int16 **sortColIdx,
+ Oid **sortOperators,
+ Oid **sortEqOperators,
+ Oid **sortCollations,
+ bool **sortNullsFirst);
+
+extern void AggSetGetPerTupleContext(FunctionCallInfo fcinfo,
+ MemoryContext *memcontext);
+
/*
* We allow plugin modules to hook function entry/exit. This is intended
* as support for loadable security policy modules, which may want to
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index bedcf04..b94e57c 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -588,6 +588,7 @@ typedef struct AggrefExprState
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ List *orddirectargs; /* Ordered direct arguments */
ExprState *aggfilter; /* FILTER expression */
int aggno; /* ID number for agg within its plan node */
} AggrefExprState;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index fc6b1d7..f0a020d 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -426,7 +426,8 @@ typedef enum NodeTag
T_WindowObjectData, /* private in nodeWindowAgg.c */
T_TIDBitmap, /* in nodes/tidbitmap.h */
T_InlineCodeBlock, /* in nodes/parsenodes.h */
- T_FdwRoutine /* in foreign/fdwapi.h */
+ T_FdwRoutine, /* in foreign/fdwapi.h */
+ T_AggStatePerAggData /* private in nodeAgg.c */
} NodeTag;
/*
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 55524b4..e8e0ff0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -303,6 +303,7 @@ typedef struct FuncCall
bool agg_star; /* argument was really '*' */
bool agg_distinct; /* arguments were labeled DISTINCT */
bool func_variadic; /* last argument was labeled VARIADIC */
+ bool has_within_group; /* WITHIN GROUP clause,if any */
struct WindowDef *over; /* OVER clause, if any */
int location; /* token location, or -1 if unknown */
} FuncCall;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 7918537..594dddf 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -247,9 +247,12 @@ typedef struct Aggref
List *args; /* arguments and sort expressions */
List *aggorder; /* ORDER BY (list of SortGroupClause) */
List *aggdistinct; /* DISTINCT (list of SortGroupClause) */
+ List *orddirectargs; /* Direct arguments for ordered set functions */
Expr *aggfilter; /* FILTER expression */
bool aggstar; /* TRUE if argument list was really '*' */
bool aggvariadic; /* TRUE if VARIADIC was used in call */
+ bool isordset; /* If node is from an ordered set function */
+ bool ishypothetical; /* If node is from a hypothetical set function */
Index agglevelsup; /* > 0 if agg belongs to outer query */
int location; /* token location, or -1 if unknown */
} Aggref;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 8bd34d6..ab27156 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -412,6 +412,7 @@ PG_KEYWORD("where", WHERE, RESERVED_KEYWORD)
PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("window", WINDOW, RESERVED_KEYWORD)
PG_KEYWORD("with", WITH, RESERVED_KEYWORD)
+PG_KEYWORD("within", WITHIN, UNRESERVED_KEYWORD)
PG_KEYWORD("without", WITHOUT, UNRESERVED_KEYWORD)
PG_KEYWORD("work", WORK, UNRESERVED_KEYWORD)
PG_KEYWORD("wrapper", WRAPPER, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h
index b6d9dd3..5fe6295 100644
--- a/src/include/parser/parse_agg.h
+++ b/src/include/parser/parse_agg.h
@@ -17,7 +17,7 @@
extern void transformAggregateCall(ParseState *pstate, Aggref *agg,
List *args, List *aggorder,
- bool agg_distinct);
+ bool agg_distinct, bool agg_within_group);
extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
WindowDef *windef);
@@ -34,4 +34,18 @@ extern void build_aggregate_fnexprs(Oid *agg_input_types,
Expr **transfnexpr,
Expr **finalfnexpr);
+void
+build_orderedset_fnexprs(Oid *agg_input_types,
+ int agg_num_inputs,
+ bool agg_variadic,
+ Oid agg_result_type,
+ Oid agg_input_collation,
+ Oid *agg_input_collation_array,
+ Oid finalfn_oid,
+ Expr **finalfnexpr);
+
+int get_aggregate_argtypes(Aggref *aggref,
+ Oid *inputTypes,
+ Oid *inputCollations);
+
#endif /* PARSE_AGG_H */
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index 9bdb033..dcc08d0 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -31,7 +31,7 @@ extern List *transformGroupClause(ParseState *pstate, List *grouplist,
ParseExprKind exprKind, bool useSQL99);
extern List *transformSortClause(ParseState *pstate, List *orderlist,
List **targetlist, ParseExprKind exprKind,
- bool resolveUnknown, bool useSQL99);
+ bool resolveUnknown, bool useSQL99, bool keepDuplicates);
extern List *transformWindowDefinitions(ParseState *pstate,
List *windowdefs,
diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h
index d33eef3..834fc92 100644
--- a/src/include/parser/parse_func.h
+++ b/src/include/parser/parse_func.h
@@ -38,14 +38,11 @@ typedef enum
FUNCDETAIL_NORMAL, /* found a matching regular function */
FUNCDETAIL_AGGREGATE, /* found a matching aggregate function */
FUNCDETAIL_WINDOWFUNC, /* found a matching window function */
- FUNCDETAIL_COERCION /* it's a type coercion request */
+ FUNCDETAIL_COERCION, /* it's a type coercion request */
} FuncDetailCode;
-
extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
- List *agg_order, Expr *agg_filter,
- bool agg_star, bool agg_distinct, bool func_variadic,
- WindowDef *over, bool is_column, int location);
+ int location, FuncCall *fn);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
@@ -66,8 +63,10 @@ extern FuncCandidateList func_select_candidate(int nargs,
extern void make_fn_arguments(ParseState *pstate,
List *fargs,
+ List *agg_order,
Oid *actual_arg_types,
- Oid *declared_arg_types);
+ Oid *declared_arg_types,
+ bool requiresUnification);
extern const char *funcname_signature_string(const char *funcname, int nargs,
List *argnames, const Oid *argtypes);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index ce3f00b..8cda3d9 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -665,6 +665,8 @@ extern Datum pg_get_functiondef(PG_FUNCTION_ARGS);
extern Datum pg_get_function_arguments(PG_FUNCTION_ARGS);
extern Datum pg_get_function_identity_arguments(PG_FUNCTION_ARGS);
extern Datum pg_get_function_result(PG_FUNCTION_ARGS);
+extern Datum pg_get_aggregate_arguments(PG_FUNCTION_ARGS);
+extern Datum pg_get_aggregate_identity_arguments(PG_FUNCTION_ARGS);
extern char *deparse_expression(Node *expr, List *dpcontext,
bool forceprefix, bool showimplicit);
extern List *deparse_context_for(const char *aliasname, Oid relid);
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 298b2e4..a97834b 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1249,6 +1249,220 @@ select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
{"(2,2,bar)","(3,1,baz)"}
(1 row)
+-- ordered set functions
+select p, percentile_cont(p) within group (order by x::float8) from generate_series(1,5) x,
+ (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p)
+ group by p order by p;
+ p | percentile_cont
+------+-----------------
+ 0 | 1
+ 0.1 | 1.4
+ 0.25 | 2
+ 0.4 | 2.6
+ 0.5 | 3
+ 0.6 | 3.4
+ 0.75 | 4
+ 0.9 | 4.6
+ 1 | 5
+(9 rows)
+
+select p, percentile_cont(p order by p) within group (order by x::float8)
+ from generate_series(1,5) x, (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p)
+ group by p order by x;
+ERROR: cannot have multiple ORDER BY clauses with WITHIN GROUP
+LINE 1: select p, percentile_cont(p order by p) within group (order ...
+ ^
+select p, sum() within group (order by x::float8)
+ from generate_series(1,5) x,
+ (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p;
+ERROR: sum(double precision) is not an ordered set function
+select p, percentile_cont(p,p) from generate_series(1,5) x,
+ (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p;
+ERROR: WITHIN GROUP is required for call to ordered set function percentile_cont
+LINE 1: select p, percentile_cont(p,p) from generate_series(1,5) x,
+ ^
+select percentile_cont(0.5) within group (order by b) from aggtest;
+ percentile_cont
+------------------
+ 53.4485001564026
+(1 row)
+
+select percentile_cont(0.5) within group (order by b),sum(b) from aggtest;
+ percentile_cont | sum
+------------------+---------
+ 53.4485001564026 | 431.773
+(1 row)
+
+select percentile_cont(0.5) within group (order by thousand) from tenk1;
+ percentile_cont
+-----------------
+ 499.5
+(1 row)
+
+select percentile_disc(0.5) within group (order by thousand) from tenk1;
+ percentile_disc
+-----------------
+ 499
+(1 row)
+
+select rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+ rank
+------
+ 5
+(1 row)
+
+select cume_dist(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+ cume_dist
+-----------
+ 0.875
+(1 row)
+
+select percent_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4),(5)) v(x);
+ percent_rank
+--------------
+ 0.5
+(1 row)
+
+select dense_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+ dense_rank
+------------
+ 3
+(1 row)
+
+select percentile_disc(array[0,0.1,0.25,0.5,0.75,0.9,1]) within group (order by thousand) from tenk1;
+ percentile_disc
+----------------------------
+ {0,99,249,499,749,899,999}
+(1 row)
+
+select percentile_cont(array[0,0.25,0.5,0.75,1]) within group (order by thousand) from tenk1;
+ percentile_cont
+-----------------------------
+ {0,249.75,499.5,749.25,999}
+(1 row)
+
+select percentile_disc(array[[null,1,0.5],[0.75,0.25,null]]) within group (order by thousand) from tenk1;
+ percentile_disc
+---------------------------------
+ {{NULL,999,499},{749,249,NULL}}
+(1 row)
+
+select percentile_cont(array[0,1,0.25,0.75,0.5,1]) within group (order by x)
+ from generate_series(1,6) x;
+ percentile_cont
+-----------------------
+ {1,6,2.25,4.75,3.5,5}
+(1 row)
+
+select ten, mode() within group (order by string4) from tenk1 group by ten;
+ ten | mode
+-----+--------
+ 0 | HHHHxx
+ 1 | OOOOxx
+ 2 | VVVVxx
+ 3 | OOOOxx
+ 4 | HHHHxx
+ 5 | HHHHxx
+ 6 | OOOOxx
+ 7 | AAAAxx
+ 8 | VVVVxx
+ 9 | VVVVxx
+(10 rows)
+
+select percentile_disc(array[0.25,0.5,0.75]) within group (order by x)
+ from unnest('{fred,jim,fred,jack,jill,fred,jill,jim,jim,sheila,jim,sheila}'::text[]) u(x);
+ percentile_disc
+-----------------
+ {fred,jill,jim}
+(1 row)
+
+-- ordered set funcs can't use ungrouped direct args:
+select rank(x) within group (order by x) from generate_series(1,5) x;
+ERROR: column "x.x" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: select rank(x) within group (order by x) from generate_serie...
+ ^
+-- collation:
+select pg_collation_for(percentile_disc(1) within group (order by x collate "POSIX"))
+ from (values ('fred'),('jim')) v(x);
+ pg_collation_for
+------------------
+ "POSIX"
+(1 row)
+
+-- hypothetical type unification and argument failures:
+select rank(3) within group (order by x) from (values ('fred'),('jim')) v(x);
+ERROR: WITHIN GROUP types text and integer cannot be matched
+LINE 1: select rank(3) within group (order by x) from (values ('fred...
+ ^
+select rank(3) within group (order by stringu1,stringu2) from tenk1;
+ERROR: function rank has 2 ordering columns but 1 hypothetical arguments
+LINE 1: select rank(3) within group (order by stringu1,stringu2) fro...
+ ^
+select rank('fred') within group (order by x) from generate_series(1,5) x;
+ERROR: invalid input syntax for integer: "fred"
+LINE 1: select rank('fred') within group (order by x) from generate_...
+ ^
+select rank('adam'::text collate "C") within group (order by x collate "POSIX")
+ from (values ('fred'),('jim')) v(x);
+ERROR: collation mismatch between explicit collations "C" and "POSIX"
+LINE 1: ...adam'::text collate "C") within group (order by x collate "P...
+ ^
+-- hypothetical type unification successes:
+select rank('adam'::varchar) within group (order by x) from (values ('fred'),('jim')) v(x);
+ rank
+------
+ 1
+(1 row)
+
+select rank('3') within group (order by x) from generate_series(1,5) x;
+ rank
+------
+ 3
+(1 row)
+
+-- divide by zero check
+select percent_rank(0) within group (order by x) from generate_series(1,0) x;
+ percent_rank
+--------------
+ 0
+(1 row)
+
+-- deparse and multiple features:
+create view aggordview1 as
+select ten,
+ percentile_disc(0.5) within group (order by thousand) as p50,
+ percentile_disc(0.5) within group (order by thousand) filter (where hundred=1) as px,
+ rank(5,'AZZZZ',50) within group (order by hundred, string4 desc, hundred)
+ from tenk1
+ group by ten order by ten;
+select pg_get_viewdef('aggordview1');
+ pg_get_viewdef
+-------------------------------------------------------------------------------------------------------------------------------
+ SELECT tenk1.ten, +
+ percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50, +
+ percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
+ rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank +
+ FROM tenk1 +
+ GROUP BY tenk1.ten +
+ ORDER BY tenk1.ten;
+(1 row)
+
+select * from aggordview1 order by ten;
+ ten | p50 | px | rank
+-----+-----+-----+------
+ 0 | 490 | | 101
+ 1 | 491 | 401 | 101
+ 2 | 492 | | 101
+ 3 | 493 | | 101
+ 4 | 494 | | 101
+ 5 | 495 | | 67
+ 6 | 496 | | 1
+ 7 | 497 | | 1
+ 8 | 498 | | 1
+ 9 | 499 | | 1
+(10 rows)
+
+drop view aggordview1;
-- variadic aggregates
select least_agg(q1,q2) from int8_tbl;
least_agg
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 57d614f..c72bd78 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -700,9 +700,17 @@ SELECT * FROM funcdescs
-- **************** pg_aggregate ****************
-- Look for illegal values in pg_aggregate fields.
+-- ordered set functions can't have transfns, and must
+-- have finalfns, but may or may not have transtypes.
+-- other aggs must have transfns and transtypes with
+-- optional finalfns.
SELECT ctid, aggfnoid::oid
FROM pg_aggregate as p1
-WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0;
+WHERE aggfnoid = 0
+ OR CASE WHEN aggisordsetfunc
+ THEN aggtransfn <> 0 OR aggfinalfn = 0
+ ELSE aggtransfn = 0 OR aggtranstype = 0
+ END;
ctid | aggfnoid
------+----------
(0 rows)
@@ -764,8 +772,9 @@ WHERE a.aggfnoid = p.oid AND
a.aggfinalfn = pfn.oid AND
(pfn.proretset
OR NOT binary_coercible(pfn.prorettype, p.prorettype)
- OR pfn.pronargs != 1
- OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]));
+ OR (aggisordsetfunc IS FALSE
+ AND (pfn.pronargs != 1
+ OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]))));
aggfnoid | proname | oid | proname
----------+---------+-----+---------
(0 rows)
@@ -857,10 +866,12 @@ ORDER BY 1;
count("any") | count()
(1 row)
--- For the same reason, we avoid creating built-in variadic aggregates.
-SELECT oid, proname
-FROM pg_proc AS p
-WHERE proisagg AND provariadic != 0;
+-- For the same reason, we avoid creating built-in variadic aggregates, except
+-- ordered set functions (which have their own syntax and are not subject to
+-- the misplaced ORDER BY issue).
+SELECT p.oid, proname
+FROM pg_proc AS p JOIN pg_aggregate AS a ON (a.aggfnoid=p.oid)
+WHERE proisagg AND provariadic != 0 AND NOT a.aggisordsetfunc;
oid | proname
-----+---------
(0 rows)
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 397edff..91d626c 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -481,6 +481,72 @@ select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
generate_series(1,2) i;
+-- ordered set functions
+
+select p, percentile_cont(p) within group (order by x::float8) from generate_series(1,5) x,
+ (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p)
+ group by p order by p;
+select p, percentile_cont(p order by p) within group (order by x::float8)
+ from generate_series(1,5) x, (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p)
+ group by p order by x;
+select p, sum() within group (order by x::float8)
+ from generate_series(1,5) x,
+ (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p;
+select p, percentile_cont(p,p) from generate_series(1,5) x,
+ (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p;
+select percentile_cont(0.5) within group (order by b) from aggtest;
+select percentile_cont(0.5) within group (order by b),sum(b) from aggtest;
+select percentile_cont(0.5) within group (order by thousand) from tenk1;
+select percentile_disc(0.5) within group (order by thousand) from tenk1;
+select rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+select cume_dist(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+select percent_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4),(5)) v(x);
+select dense_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+
+select percentile_disc(array[0,0.1,0.25,0.5,0.75,0.9,1]) within group (order by thousand) from tenk1;
+select percentile_cont(array[0,0.25,0.5,0.75,1]) within group (order by thousand) from tenk1;
+select percentile_disc(array[[null,1,0.5],[0.75,0.25,null]]) within group (order by thousand) from tenk1;
+select percentile_cont(array[0,1,0.25,0.75,0.5,1]) within group (order by x)
+ from generate_series(1,6) x;
+
+select ten, mode() within group (order by string4) from tenk1 group by ten;
+
+select percentile_disc(array[0.25,0.5,0.75]) within group (order by x)
+ from unnest('{fred,jim,fred,jack,jill,fred,jill,jim,jim,sheila,jim,sheila}'::text[]) u(x);
+
+-- ordered set funcs can't use ungrouped direct args:
+select rank(x) within group (order by x) from generate_series(1,5) x;
+
+-- collation:
+select pg_collation_for(percentile_disc(1) within group (order by x collate "POSIX"))
+ from (values ('fred'),('jim')) v(x);
+
+-- hypothetical type unification and argument failures:
+select rank(3) within group (order by x) from (values ('fred'),('jim')) v(x);
+select rank(3) within group (order by stringu1,stringu2) from tenk1;
+select rank('fred') within group (order by x) from generate_series(1,5) x;
+select rank('adam'::text collate "C") within group (order by x collate "POSIX")
+ from (values ('fred'),('jim')) v(x);
+-- hypothetical type unification successes:
+select rank('adam'::varchar) within group (order by x) from (values ('fred'),('jim')) v(x);
+select rank('3') within group (order by x) from generate_series(1,5) x;
+
+-- divide by zero check
+select percent_rank(0) within group (order by x) from generate_series(1,0) x;
+
+-- deparse and multiple features:
+create view aggordview1 as
+select ten,
+ percentile_disc(0.5) within group (order by thousand) as p50,
+ percentile_disc(0.5) within group (order by thousand) filter (where hundred=1) as px,
+ rank(5,'AZZZZ',50) within group (order by hundred, string4 desc, hundred)
+ from tenk1
+ group by ten order by ten;
+
+select pg_get_viewdef('aggordview1');
+select * from aggordview1 order by ten;
+drop view aggordview1;
+
-- variadic aggregates
select least_agg(q1,q2) from int8_tbl;
select least_agg(variadic array[q1,q2]) from int8_tbl;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index efcd70f..0b2cba7 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -564,10 +564,18 @@ SELECT * FROM funcdescs
-- **************** pg_aggregate ****************
-- Look for illegal values in pg_aggregate fields.
+-- ordered set functions can't have transfns, and must
+-- have finalfns, but may or may not have transtypes.
+-- other aggs must have transfns and transtypes with
+-- optional finalfns.
SELECT ctid, aggfnoid::oid
FROM pg_aggregate as p1
-WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0;
+WHERE aggfnoid = 0
+ OR CASE WHEN aggisordsetfunc
+ THEN aggtransfn <> 0 OR aggfinalfn = 0
+ ELSE aggtransfn = 0 OR aggtranstype = 0
+ END;
-- Make sure the matching pg_proc entry is sensible, too.
@@ -618,8 +626,9 @@ WHERE a.aggfnoid = p.oid AND
a.aggfinalfn = pfn.oid AND
(pfn.proretset
OR NOT binary_coercible(pfn.prorettype, p.prorettype)
- OR pfn.pronargs != 1
- OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]));
+ OR (aggisordsetfunc IS FALSE
+ AND (pfn.pronargs != 1
+ OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]))));
-- If transfn is strict then either initval should be non-NULL, or
-- input type should match transtype so that the first non-null input
@@ -685,11 +694,13 @@ WHERE p1.oid < p2.oid AND p1.proname = p2.proname AND
array_dims(p1.proargtypes) != array_dims(p2.proargtypes)
ORDER BY 1;
--- For the same reason, we avoid creating built-in variadic aggregates.
+-- For the same reason, we avoid creating built-in variadic aggregates, except
+-- ordered set functions (which have their own syntax and are not subject to
+-- the misplaced ORDER BY issue).
-SELECT oid, proname
-FROM pg_proc AS p
-WHERE proisagg AND provariadic != 0;
+SELECT p.oid, proname
+FROM pg_proc AS p JOIN pg_aggregate AS a ON (a.aggfnoid=p.oid)
+WHERE proisagg AND provariadic != 0 AND NOT a.aggisordsetfunc;
-- For the same reason, built-in aggregates with default arguments are no good.
On Fri, 2013-11-15 at 00:05 +0530, Atri Sharma wrote:
Please find the latest version of the patch. This version fixes the
issues pointed out by the reviewer and the divide by zero bug in
percent_rank function. This version also adds a regression test for
the divide by zero case in percent_rank.
This patch doesn't apply.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Nov 18, 2013 at 9:26 AM, Peter Eisentraut <peter_e@gmx.net> wrote:
On Fri, 2013-11-15 at 00:05 +0530, Atri Sharma wrote:
Please find the latest version of the patch. This version fixes the
issues pointed out by the reviewer and the divide by zero bug in
percent_rank function. This version also adds a regression test for
the divide by zero case in percent_rank.This patch doesn't apply.
Hi all,
Please find attached the latest patch for WITHIN GROUP. This patch is
after fixing the merge conflicts.
Regards,
Atri
--
Regards,
Atri
l'apprenant
Attachments:
withingrouppatch211113context.patchtext/x-diff; charset=US-ASCII; name=withingrouppatch211113context.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>aggtransspace</structfield></entry>
--- 370,394 ----
<entry><structfield>aggtranstype</structfield></entry>
<entry><type>oid</type></entry>
<entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
! <entry>Data type of the aggregate function's internal transition (state) data (zero if none)</entry>
! </row>
! <row>
! <entry><structfield>aggtranssortop</structfield></entry>
! <entry><type>oid</type></entry>
! <entry><literal><link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.oid</literal></entry>
! <entry>An optional sort operator for the type "aggtranstype", used for some kinds of ordered set functions</entry>
! </row>
! <row>
! <entry><structfield>aggordnargs</structfield></entry>
! <entry><type>int4</type></entry>
! <entry></entry>
! <entry>Number of direct arguments to ordered set function; -2 for hypothetical set functions; -1 for ordinary aggregates.</entry>
! </row>
! <row>
! <entry><structfield>aggisordsetfunc</structfield></entry>
! <entry><type>bool</type></entry>
! <entry></entry>
! <entry>A flag to represent whether a function is ordered set or not</entry>
</row>
<row>
<entry><structfield>aggtransspace</structfield></entry>
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 12294,12299 **** SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab;
--- 12294,12542 ----
</sect1>
+ <sect1 id="functions-ordered">
+ <title>Ordered Set Functions</title>
+
+ <indexterm zone="functions-ordered">
+ <primary>ordered set function</primary>
+ <secondary>built-in</secondary>
+ </indexterm>
+
+ <para>
+ <firstterm>Ordered set functions</firstterm> compute a single result
+ from an ordered set of input values. The built-in ordered set functions
+ are listed in
+ <xref linkend="functions-inversedist-table"> and
+ <xref linkend="functions-hypothetical-table">.
+ The special syntax considerations for ordered set functions
+ are explained in <xref linkend="syntax-orderedset">.
+ </para>
+
+ <table id="functions-inversedist-table">
+ <title>Inverse Distribution Functions</title>
+
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Function</entry>
+ <entry>Direct Argument Type(s)</entry>
+ <entry>Ordered Argument Type(s)</entry>
+ <entry>Return Type</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>percentile</primary>
+ <secondary>discrete</secondary>
+ </indexterm>
+ <function>percentile_disc(<replaceable class="parameter">fraction</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>double precision</type> (must be [0..1])
+ </entry>
+ <entry>
+ any sortable type
+ </entry>
+ <entry>
+ same as sort expression
+ </entry>
+ <entry>
+ discrete percentile; returns the first result whose position in
+ the ordering equals or exceeds the specified fraction
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <function>percentile_disc(<replaceable class="parameter">fractions</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>double precision[]</type> (all must be [0..1] or null)
+ </entry>
+ <entry>
+ any sortable type
+ </entry>
+ <entry>
+ array of input type
+ </entry>
+ <entry>
+ multiple discrete percentile; returns an array of results matching the
+ shape of the <literal>fractions</literal> parameter, with each
+ non-null element replaced by the input value at that percentile
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>percentile</primary>
+ <secondary>continuous</secondary>
+ </indexterm>
+ <indexterm>
+ <primary>median</primary>
+ </indexterm>
+ <function>percentile_cont(<replaceable class="parameter">fraction</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>double precision</type> (must be [0..1])
+ </entry>
+ <entry>
+ <type>double precision</type> or <type>interval</type>
+ </entry>
+ <entry>
+ same as sort expression
+ </entry>
+ <entry>
+ continuous percentile; interpolates between adjacent items.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <function>percentile_cont(<replaceable class="parameter">fractions</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>double precision[]</type> (all must be [0..1] or null)
+ </entry>
+ <entry>
+ <type>double precision</type> or <type>interval</type>
+ </entry>
+ <entry>
+ array of input type
+ </entry>
+ <entry>
+ multiple continuous percentile; returns an array of results matching
+ the shape of the <literal>fractions</literal> parameter, with each
+ non-null element replaced by the value corresponding to that percentile
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>mode</primary>
+ <secondary>statistical</secondary>
+ </indexterm>
+ <function>mode() WITHIN GROUP (ORDER BY <replaceable class="parameter">sort_expression</replaceable>)</function>
+ </entry>
+ <entry>
+ </entry>
+ <entry>
+ any sortable type
+ </entry>
+ <entry>
+ same as sort expression
+ </entry>
+ <entry>
+ returns the most frequent input value (choosing one arbitrarily if
+ there are multiple equally good results)
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ All the inverse distribution functions ignore null values in their sorted
+ input. The <replaceable>fraction</replaceable> parameter must be between 0
+ and 1; an error is thrown if not. However, a null fraction simply produces
+ a null result.
+ </para>
+
+ <table id="functions-hypothetical-table">
+ <title>Hypothetical Set Functions</title>
+
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Function</entry>
+ <entry>Return Type</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>rank</primary>
+ <secondary>hypothetical</secondary>
+ </indexterm>
+ <function>rank(<replaceable class="parameter">args</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sorted_args</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>bigint</type>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>dense_rank</primary>
+ <secondary>hypothetical</secondary>
+ </indexterm>
+ <function>dense_rank(<replaceable class="parameter">args</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sorted_args</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>bigint</type>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>percent_rank</primary>
+ <secondary>hypothetical</secondary>
+ </indexterm>
+ <function>percent_rank(<replaceable class="parameter">args</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sorted_args</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>double precision</type>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>cume_dist</primary>
+ <secondary>hypothetical</secondary>
+ </indexterm>
+ <function>cume_dist(<replaceable class="parameter">args</replaceable>) WITHIN GROUP (ORDER BY <replaceable class="parameter">sorted_args</replaceable>)</function>
+ </entry>
+ <entry>
+ <type>double precision</type>
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ For all hypothetical set functions, the list of arguments given
+ by <replaceable>args</replaceable> should match the number and types of
+ arguments given as <replaceable>sorted_args</replaceable>.
+ </para>
+
+ <para>
+ All of the functions listed in
+ <xref linkend="functions-hypothetical-table"> are associated with a
+ window function defined in
+ <xref linkend="functions-window">. In each case, the function result
+ represents the value that the associated window function would have
+ returned, for the hypothetical row constructed from
+ <replaceable>args</replaceable> and included in the sorted group of
+ rows.
+ </para>
+
+ </sect1>
+
<sect1 id="functions-window">
<title>Window Functions</title>
*** a/doc/src/sgml/ref/alter_aggregate.sgml
--- b/doc/src/sgml/ref/alter_aggregate.sgml
***************
*** 21,32 **** PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
! ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
RENAME TO <replaceable>new_name</replaceable>
ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
OWNER TO <replaceable>new_owner</replaceable>
ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
SET SCHEMA <replaceable>new_schema</replaceable>
</synopsis>
</refsynopsisdiv>
--- 21,37 ----
<refsynopsisdiv>
<synopsis>
! ALTER AGGREGATE
RENAME TO <replaceable>new_name</replaceable>
ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
OWNER TO <replaceable>new_owner</replaceable>
ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
SET SCHEMA <replaceable>new_schema</replaceable>
+
+ <phrase>where <replaceable>aggregate_signature</replaceable> is one of:</phrase>
+
+ <replaceable>name</replaceable> ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
+ <replaceable>name</replaceable> ( [ [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] ] ) WITHIN GROUP ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
</synopsis>
</refsynopsisdiv>
***************
*** 148,157 **** ALTER AGGREGATE myavg(integer) OWNER TO joe;
</para>
<para>
! To move the aggregate function <literal>myavg</literal> for type
! <type>integer</type> into schema <literal>myschema</literal>:
<programlisting>
! ALTER AGGREGATE myavg(integer) SET SCHEMA myschema;
</programlisting></para>
</refsect1>
--- 153,163 ----
</para>
<para>
! To move the ordered set function <literal>mypercentile</literal> with
! direct argument of type <type>float8</type> taking groups
! of <type>integer</type> type into schema <literal>myschema</literal>:
<programlisting>
! ALTER AGGREGATE mypercentile(float8) WITHIN GROUP (integer) SET SCHEMA myschema;
</programlisting></para>
</refsect1>
*** a/doc/src/sgml/ref/alter_extension.sgml
--- b/doc/src/sgml/ref/alter_extension.sgml
***************
*** 30,36 **** ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> DROP <replacea
<phrase>where <replaceable class="PARAMETER">member_object</replaceable> is:</phrase>
! AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) |
CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
CONVERSION <replaceable class="PARAMETER">object_name</replaceable> |
--- 30,36 ----
<phrase>where <replaceable class="PARAMETER">member_object</replaceable> is:</phrase>
! AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) [ WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) ] |
CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
CONVERSION <replaceable class="PARAMETER">object_name</replaceable> |
*** a/doc/src/sgml/ref/comment.sgml
--- b/doc/src/sgml/ref/comment.sgml
***************
*** 23,29 **** PostgreSQL documentation
<synopsis>
COMMENT ON
{
! AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) |
CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
COLUMN <replaceable class="PARAMETER">relation_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
--- 23,29 ----
<synopsis>
COMMENT ON
{
! AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) [ WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) ] |
CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
COLLATION <replaceable class="PARAMETER">object_name</replaceable> |
COLUMN <replaceable class="PARAMETER">relation_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
*** a/doc/src/sgml/ref/create_aggregate.sgml
--- b/doc/src/sgml/ref/create_aggregate.sgml
***************
*** 30,35 **** CREATE AGGREGATE <replaceable class="parameter">name</replaceable> ( [ <replacea
--- 30,44 ----
[ , SORTOP = <replaceable class="PARAMETER">sort_operator</replaceable> ]
)
+ CREATE AGGREGATE <replaceable class="parameter">name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">arg_name</replaceable> ] <replaceable class="parameter">arg_data_type</replaceable> [ , ... ] ) WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">arg_name</replaceable> ] <replaceable class="parameter">arg_data_type</replaceable> [ , ... ] ) (
+ FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable>
+ [ , STRICT ]
+ [ , HYPOTHETICAL ]
+ [ , STYPE = <replaceable class="PARAMETER">state_data_type</replaceable> ]
+ [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
+ [ , TRANSSORTOP = <replaceable class="PARAMETER">state_sort_operator</replaceable> ]
+ )
+
<phrase>or the old syntax</phrase>
CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
***************
*** 72,78 **** CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
</para>
<para>
! An aggregate function is made from one or two ordinary
functions:
a state transition function
<replaceable class="PARAMETER">sfunc</replaceable>,
--- 81,87 ----
</para>
<para>
! An ordinary aggregate function is made from one or two ordinary
functions:
a state transition function
<replaceable class="PARAMETER">sfunc</replaceable>,
***************
*** 167,172 **** SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
--- 176,189 ----
</para>
<para>
+ The <literal>WITHIN GROUP</literal> syntax denotes a special subset of
+ aggregate functions collectively called <quote>ordered set
+ functions</quote>. These functions operate over groups of sorted values
+ in order-dependent ways. As such, they are constructed differently; there
+ is no state transition function, but the final function is required.
+ </para>
+
+ <para>
To be able to create an aggregate function, you must
have <literal>USAGE</literal> privilege on the argument types, the state
type, and the return type, as well as <literal>EXECUTE</literal> privilege
***************
*** 296,301 **** SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
--- 313,323 ----
aggregate's result, and the return type is <replaceable
class="PARAMETER">state_data_type</replaceable>.
</para>
+ <para>
+ For ordered set functions, the function arguments must instead
+ correspond to the input arguments (both direct and grouped) plus
+ the state type if any.
+ </para>
</listitem>
</varlistentry>
***************
*** 323,328 **** SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
--- 345,383 ----
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">state_sort_operator</replaceable></term>
+ <listitem>
+ <para>
+ For ordered set functions only, this is a sort operator that can be
+ applied to
+ the <replaceable class="PARAMETER">state_data_type</replaceable>.
+ This is just an operator name (possibly schema-qualified).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>STRICT</literal></term>
+ <listitem>
+ <para>
+ For ordered set functions only, this flag specifies that the function is
+ strict, i.e. that grouped rows containing nulls are skipped.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>HYPOTHETICAL</literal></term>
+ <listitem>
+ <para>
+ For ordered set functions only, this flag specifies that the aggregate
+ parameters are to be processed according to the requirements for
+ hypothetical set functions.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
<para>
*** a/doc/src/sgml/ref/drop_aggregate.sgml
--- b/doc/src/sgml/ref/drop_aggregate.sgml
***************
*** 21,29 **** PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
! DROP AGGREGATE [ IF EXISTS ]
! <replaceable class="parameter">name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">arg_name</replaceable> ] <replaceable class="parameter">arg_data_type</replaceable> [ , ... ] )
[ CASCADE | RESTRICT ]
</synopsis>
</refsynopsisdiv>
--- 21,33 ----
<refsynopsisdiv>
<synopsis>
! DROP AGGREGATE [ IF EXISTS ] <replaceable class="parameter">aggregate_signature</replaceable>
[ CASCADE | RESTRICT ]
+
+ <phrase>where <replaceable>aggregate_signature</replaceable> is one of:</phrase>
+
+ <replaceable>name</replaceable> ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
+ <replaceable>name</replaceable> ( [ [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] ] ) WITHIN GROUP ( * | [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] )
</synopsis>
</refsynopsisdiv>
*** a/doc/src/sgml/ref/security_label.sgml
--- b/doc/src/sgml/ref/security_label.sgml
***************
*** 25,31 **** SECURITY LABEL [ FOR <replaceable class="PARAMETER">provider</replaceable> ] ON
{
TABLE <replaceable class="PARAMETER">object_name</replaceable> |
COLUMN <replaceable class="PARAMETER">table_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
! AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) |
DATABASE <replaceable class="PARAMETER">object_name</replaceable> |
DOMAIN <replaceable class="PARAMETER">object_name</replaceable> |
EVENT TRIGGER <replaceable class="PARAMETER">object_name</replaceable> |
--- 25,31 ----
{
TABLE <replaceable class="PARAMETER">object_name</replaceable> |
COLUMN <replaceable class="PARAMETER">table_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
! AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) [ WITHIN GROUP ( * | [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) ] |
DATABASE <replaceable class="PARAMETER">object_name</replaceable> |
DOMAIN <replaceable class="PARAMETER">object_name</replaceable> |
EVENT TRIGGER <replaceable class="PARAMETER">object_name</replaceable> |
*** a/doc/src/sgml/syntax.sgml
--- b/doc/src/sgml/syntax.sgml
***************
*** 1706,1711 **** SELECT string_agg(a ORDER BY a, ',') FROM table; -- incorrect
--- 1706,1759 ----
</para>
</sect2>
+ <sect2 id="syntax-orderedset">
+ <title>Ordered Set Functions</title>
+
+ <indexterm zone="syntax-orderedset">
+ <primary>ordered set function</primary>
+ </indexterm>
+
+ <indexterm zone="syntax-orderedset">
+ <primary>aggregate function</primary>
+ <secondary>ordered set function</secondary>
+ </indexterm>
+
+ <indexterm zone="syntax-orderedset">
+ <primary>WITHIN GROUP</primary>
+ </indexterm>
+
+ <para>
+ An <firstterm>ordered set function</firstterm> is a particular kind of
+ aggregate function which is applied to sorted groups of values and returns
+ a single result for each group which may be influenced by the sort
+ order. Like all aggregate functions, it reduces multiple inputs to a
+ single output value; typical ordered set functions return a percentile
+ extracted from the ordered group, or the rank a specified value would have
+ within that group. The syntax of an ordered set function is:
+
+ <synopsis>
+ <replaceable>function_name</replaceable> ( [ <replaceable>expression</replaceable> [ , ... ] ] ) WITHIN GROUP ( <replaceable>order_by_clause</replaceable> ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+ </synopsis>
+
+ where <replaceable>function_name</replaceable> is a previously
+ defined ordered set function (possibly qualified with a schema name) and
+ <replaceable>expression</replaceable> is any value expression that does
+ not itself contain an aggregate expression, a window function call, or any
+ reference to ungrouped columns of the source data. The
+ mandatory <replaceable>order_by_clause</replaceable> has the same syntax
+ as for a query-level <literal>ORDER BY</> clause, as described
+ in <xref linkend="queries-order">, except that its expressions are always
+ just expressions and cannot be output-column names or numbers. The
+ expressions of the <replaceable>order_by_clause</replaceable> may, and
+ almost invariably do, refer to the ungrouped columns of the input; the
+ clause defines the grouped input to the function. The optional
+ <replaceable>filter_clause</replaceable> is identical to that for
+ aggregate functions (see <xref linkend="syntax-aggregates">, and is applied
+ to input rows prior to the sort operation.
+ </para>
+
+ </sect2>
+
<sect2 id="syntax-window-functions">
<title>Window Function Calls</title>
*** a/doc/src/sgml/xaggr.sgml
--- b/doc/src/sgml/xaggr.sgml
***************
*** 9,28 ****
</indexterm>
<para>
! Aggregate functions in <productname>PostgreSQL</productname>
! are expressed in terms of <firstterm>state values</firstterm>
! and <firstterm>state transition functions</firstterm>.
! That is, an aggregate operates using a state value that is updated
! as each successive input row is processed.
! To define a new aggregate
! function, one selects a data type for the state value,
! an initial value for the state, and a state transition
! function. The state transition function is just an
! ordinary function that could also be used outside the
! context of the aggregate. A <firstterm>final function</firstterm>
! can also be specified, in case the desired result of the aggregate
! is different from the data that needs to be kept in the running
! state value.
</para>
<para>
--- 9,25 ----
</indexterm>
<para>
! Aggregate functions (other than ordered set functions)
! in <productname>PostgreSQL</productname> are expressed in terms
! of <firstterm>state values</firstterm> and <firstterm>state transition
! functions</firstterm>. That is, an aggregate operates using a state value
! that is updated as each successive input row is processed. To define a new
! aggregate function, one selects a data type for the state value, an initial
! value for the state, and a state transition function. The state transition
! function is just an ordinary function that could also be used outside the
! context of the aggregate. A <firstterm>final function</firstterm> can also
! be specified, in case the desired result of the aggregate is different from
! the data that needs to be kept in the running state value.
</para>
<para>
*** a/src/backend/catalog/pg_aggregate.c
--- b/src/backend/catalog/pg_aggregate.c
***************
*** 46,51 **** Oid
--- 46,52 ----
AggregateCreate(const char *aggName,
Oid aggNamespace,
int numArgs,
+ int numDirectArgs,
oidvector *parameterTypes,
Datum allParameterTypes,
Datum parameterModes,
***************
*** 54,78 **** AggregateCreate(const char *aggName,
List *aggtransfnName,
List *aggfinalfnName,
List *aggsortopName,
Oid aggTransType,
int32 aggTransSpace,
! const char *agginitval)
{
Relation aggdesc;
HeapTuple tup;
bool nulls[Natts_pg_aggregate];
Datum values[Natts_pg_aggregate];
Form_pg_proc proc;
! Oid transfn;
! Oid finalfn = InvalidOid; /* can be omitted */
! Oid sortop = InvalidOid; /* can be omitted */
Oid *aggArgTypes = parameterTypes->values;
bool hasPolyArg;
bool hasInternalArg;
Oid rettype;
Oid finaltype;
! Oid *fnArgs;
! int nargs_transfn;
Oid procOid;
TupleDesc tupDesc;
int i;
--- 55,84 ----
List *aggtransfnName,
List *aggfinalfnName,
List *aggsortopName,
+ List *aggtranssortopName,
Oid aggTransType,
int32 aggTransSpace,
! const char *agginitval,
! bool isStrict,
! bool isOrderedSet,
! bool isHypotheticalSet)
{
Relation aggdesc;
HeapTuple tup;
bool nulls[Natts_pg_aggregate];
Datum values[Natts_pg_aggregate];
Form_pg_proc proc;
! Oid transfn = InvalidOid; /* can be omitted */
! Oid finalfn = InvalidOid; /* can be omitted */
! Oid sortop = InvalidOid; /* can be omitted */
! Oid transsortop = InvalidOid; /* can be omitted */
Oid *aggArgTypes = parameterTypes->values;
bool hasPolyArg;
bool hasInternalArg;
+ Oid variadic_type = InvalidOid;
Oid rettype;
Oid finaltype;
! Oid *fnArgs = palloc((numArgs + 1) * sizeof(Oid));
Oid procOid;
TupleDesc tupDesc;
int i;
***************
*** 84,91 **** AggregateCreate(const char *aggName,
if (!aggName)
elog(ERROR, "no aggregate name supplied");
! if (!aggtransfnName)
! elog(ERROR, "aggregate must have a transition function");
/* check for polymorphic and INTERNAL arguments */
hasPolyArg = false;
--- 90,109 ----
if (!aggName)
elog(ERROR, "no aggregate name supplied");
! if (isOrderedSet)
! {
! if (aggtransfnName)
! elog(ERROR, "ordered set functions cannot have transition functions");
! if (!aggfinalfnName)
! elog(ERROR, "ordered set functions must have final functions");
! }
! else
! {
! if (!aggtransfnName)
! elog(ERROR, "aggregate must have a transition function");
! if (isStrict)
! elog(ERROR, "aggregate with transition function must not be explicitly STRICT");
! }
/* check for polymorphic and INTERNAL arguments */
hasPolyArg = false;
***************
*** 98,103 **** AggregateCreate(const char *aggName,
--- 116,251 ----
hasInternalArg = true;
}
+ /*-
+ * Argument mode checks. If there were no variadics, we should have been
+ * passed a NULL pointer for parameterModes, so we can skip this if so.
+ * Otherwise, the allowed cases are as follows:
+ *
+ * aggfn(..., variadic sometype) - normal agg with variadic arg last
+ * aggfn(..., variadic "any") - normal agg with "any" variadic
+ *
+ * ordfn(..., variadic "any") within group (*)
+ * - ordered set func with "any" variadic in direct args, which requires
+ * that the ordered args also be variadic any which we represent
+ * specially; this is the common case for hypothetical set functions.
+ * Note this is the only case where numDirectArgs == numArgs on input
+ * (implies finalfn(..., variadic "any"))
+ *
+ * ordfn(...) within group (..., variadic "any")
+ * - ordered set func with no variadic in direct args, but allowing any
+ * types of ordered args.
+ * (implies finalfn(..., ..., variadic "any"))
+ *
+ * We don't allow variadic ordered args other than "any"; we don't allow
+ * anything after variadic "any" except the special-case (*).
+ *
+ * We might like to support this one:
+ *
+ * ordfn(..., variadic sometype) within group (...)
+ * - ordered set func with variadic direct arg last, followed by ordered
+ * args, none of which are variadic
+ * (implies finalfn(..., sometype, ..., [transtype]))
+ *
+ * but currently it seems to be too intrusive to do so; the assumption
+ * that variadic args can only come last is quite widespread.
+ */
+
+ if (parameterModes != PointerGetDatum(NULL))
+ {
+ /*
+ * We expect the array to be a 1-D CHAR array; verify that. We don't
+ * need to use deconstruct_array() since the array data is just going
+ * to look like a C array of char values.
+ */
+ ArrayType *modesArray = (ArrayType *) DatumGetPointer(parameterModes);
+ char *paramModes;
+ int modesCount;
+ int i;
+
+ if (ARR_NDIM(modesArray) != 1 ||
+ ARR_HASNULL(modesArray) ||
+ ARR_ELEMTYPE(modesArray) != CHAROID)
+ elog(ERROR, "parameterModes is not a 1-D char array");
+
+ paramModes = (char *) ARR_DATA_PTR(modesArray);
+ modesCount = ARR_DIMS(modesArray)[0];
+
+ for (i = 0; i < modesCount; ++i)
+ {
+ switch (paramModes[i])
+ {
+ case PROARGMODE_VARIADIC:
+ if (OidIsValid(variadic_type))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("VARIADIC must not be specified more than once")));
+ variadic_type = aggArgTypes[i];
+
+ /* enforce restrictions on ordered args */
+
+ if (numDirectArgs >= 0
+ && i >= numDirectArgs
+ && variadic_type != ANYOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("VARIADIC ordered arguments must be of type ANY")));
+
+ break;
+
+ case PROARGMODE_IN:
+ if (OidIsValid(variadic_type))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("VARIADIC argument must be last")));
+ break;
+
+ default:
+ elog(ERROR, "invalid argument mode");
+ }
+ }
+ }
+
+ switch (variadic_type)
+ {
+ case InvalidOid:
+ case ANYARRAYOID:
+ case ANYOID:
+ /* okay */
+ break;
+ default:
+ if (!OidIsValid(get_element_type(variadic_type)))
+ elog(ERROR, "VARIADIC parameter must be an array");
+ break;
+ }
+
+ if (isHypotheticalSet)
+ {
+ if (numArgs != numDirectArgs
+ || variadic_type != ANYOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("invalid argument types for hypothetical set function"),
+ errhint("Required declaration is (..., VARIADIC \"any\") WITHIN GROUP (*)")));
+
+ /* flag for special processing for hypothetical sets */
+ numDirectArgs = -2;
+ }
+ else if (numArgs == numDirectArgs)
+ {
+ if (variadic_type == ANYOID)
+ {
+ /*
+ * this case allows the number of direct args to be truly variable
+ */
+ numDirectArgs = -1;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("invalid argument types for ordered set function"),
+ errhint("WITHIN GROUP (*) is not allowed without VARIADIC \"any\"")));
+ }
+
/*
* If transtype is polymorphic, must have polymorphic argument also; else
* we will have no way to deduce the actual transtype.
***************
*** 108,160 **** AggregateCreate(const char *aggName,
errmsg("cannot determine transition data type"),
errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument.")));
! /* find the transfn */
! nargs_transfn = numArgs + 1;
! fnArgs = (Oid *) palloc(nargs_transfn * sizeof(Oid));
! fnArgs[0] = aggTransType;
! memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
! transfn = lookup_agg_function(aggtransfnName, nargs_transfn, fnArgs,
! &rettype);
! /*
! * Return type of transfn (possibly after refinement by
! * enforce_generic_type_consistency, if transtype isn't polymorphic) must
! * exactly match declared transtype.
! *
! * In the non-polymorphic-transtype case, it might be okay to allow a
! * rettype that's binary-coercible to transtype, but I'm not quite
! * convinced that it's either safe or useful. When transtype is
! * polymorphic we *must* demand exact equality.
! */
! if (rettype != aggTransType)
! ereport(ERROR,
! (errcode(ERRCODE_DATATYPE_MISMATCH),
! errmsg("return type of transition function %s is not %s",
! NameListToString(aggtransfnName),
! format_type_be(aggTransType))));
! tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn));
! if (!HeapTupleIsValid(tup))
! elog(ERROR, "cache lookup failed for function %u", transfn);
! proc = (Form_pg_proc) GETSTRUCT(tup);
! /*
! * If the transfn is strict and the initval is NULL, make sure first input
! * type and transtype are the same (or at least binary-compatible), so
! * that it's OK to use the first input value as the initial transValue.
! */
! if (proc->proisstrict && agginitval == NULL)
! {
! if (numArgs < 1 ||
! !IsBinaryCoercible(aggArgTypes[0], aggTransType))
ereport(ERROR,
! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
}
- ReleaseSysCache(tup);
/* handle finalfn, if supplied */
! if (aggfinalfnName)
{
fnArgs[0] = aggTransType;
finalfn = lookup_agg_function(aggfinalfnName, 1, fnArgs,
--- 256,341 ----
errmsg("cannot determine transition data type"),
errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument.")));
! if (!isOrderedSet)
! {
! /* find the transfn */
! fnArgs[0] = aggTransType;
! memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
! transfn = lookup_agg_function(aggtransfnName, numArgs + 1, fnArgs,
! &rettype);
! /*
! * Return type of transfn (possibly after refinement by
! * enforce_generic_type_consistency, if transtype isn't polymorphic)
! * must exactly match declared transtype.
! *
! * In the non-polymorphic-transtype case, it might be okay to allow a
! * rettype that's binary-coercible to transtype, but I'm not quite
! * convinced that it's either safe or useful. When transtype is
! * polymorphic we *must* demand exact equality.
! */
! if (rettype != aggTransType)
ereport(ERROR,
! (errcode(ERRCODE_DATATYPE_MISMATCH),
! errmsg("return type of transition function %s is not %s",
! NameListToString(aggtransfnName),
! format_type_be(aggTransType))));
!
! tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn));
! if (!HeapTupleIsValid(tup))
! elog(ERROR, "cache lookup failed for function %u", transfn);
! proc = (Form_pg_proc) GETSTRUCT(tup);
!
! /*
! * If the transfn is strict and the initval is NULL, make sure first
! * input type and transtype are the same (or at least
! * binary-compatible), so that it's OK to use the first input value as
! * the initial transValue.
! */
! if (proc->proisstrict && agginitval == NULL)
! {
! if (numArgs < 1 ||
! !IsBinaryCoercible(aggArgTypes[0], aggTransType))
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
! }
! ReleaseSysCache(tup);
}
/* handle finalfn, if supplied */
! if (isOrderedSet)
! {
! int num_final_args = numArgs;
!
! memcpy(fnArgs, aggArgTypes, num_final_args * sizeof(Oid));
!
! /*
! * If there's a transtype, it becomes the last arg to the finalfn;
! * but if the agg (and hence the finalfn) is variadic "any", then
! * this contributes nothing to the signature.
! */
! if (aggTransType != InvalidOid && variadic_type != ANYOID)
! fnArgs[num_final_args++] = aggTransType;
!
! finalfn = lookup_agg_function(aggfinalfnName, num_final_args, fnArgs,
! &finaltype);
!
! /*
! * This is also checked at runtime for security reasons, but check
! * here too to provide a friendly error (the requirement is because
! * the finalfn will be passed null dummy args for type resolution
! * purposes).
! */
!
! if (func_strict(finalfn))
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! errmsg("ordered set final functions must not be declared STRICT")));
! }
! else if (aggfinalfnName)
{
fnArgs[0] = aggTransType;
finalfn = lookup_agg_function(aggfinalfnName, 1, fnArgs,
***************
*** 167,172 **** AggregateCreate(const char *aggName,
--- 348,354 ----
*/
finaltype = aggTransType;
}
+
Assert(OidIsValid(finaltype));
/*
***************
*** 208,213 **** AggregateCreate(const char *aggName,
--- 390,407 ----
false, -1);
}
+ /* handle transsortop, if supplied */
+ if (aggtranssortopName)
+ {
+ if (!isOrderedSet || !OidIsValid(aggTransType))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("transition sort operator can only be specified for ordered set functions with transition types")));
+ transsortop = LookupOperName(NULL, aggtranssortopName,
+ aggTransType, aggTransType,
+ false, -1);
+ }
+
/*
* permission checks on used types
*/
***************
*** 218,232 **** AggregateCreate(const char *aggName,
aclcheck_error_type(aclresult, aggArgTypes[i]);
}
! aclresult = pg_type_aclcheck(aggTransType, GetUserId(), ACL_USAGE);
! if (aclresult != ACLCHECK_OK)
! aclcheck_error_type(aclresult, aggTransType);
aclresult = pg_type_aclcheck(finaltype, GetUserId(), ACL_USAGE);
if (aclresult != ACLCHECK_OK)
aclcheck_error_type(aclresult, finaltype);
-
/*
* Everything looks okay. Try to create the pg_proc entry for the
* aggregate. (This could fail if there's already a conflicting entry.)
--- 412,428 ----
aclcheck_error_type(aclresult, aggArgTypes[i]);
}
! if (OidIsValid(aggTransType))
! {
! aclresult = pg_type_aclcheck(aggTransType, GetUserId(), ACL_USAGE);
! if (aclresult != ACLCHECK_OK)
! aclcheck_error_type(aclresult, aggTransType);
! }
aclresult = pg_type_aclcheck(finaltype, GetUserId(), ACL_USAGE);
if (aclresult != ACLCHECK_OK)
aclcheck_error_type(aclresult, finaltype);
/*
* Everything looks okay. Try to create the pg_proc entry for the
* aggregate. (This could fail if there's already a conflicting entry.)
***************
*** 247,253 **** AggregateCreate(const char *aggName,
false, /* security invoker (currently not
* definable for agg) */
false, /* isLeakProof */
! false, /* isStrict (not needed for agg) */
PROVOLATILE_IMMUTABLE, /* volatility (not
* needed for agg) */
parameterTypes, /* paramTypes */
--- 443,449 ----
false, /* security invoker (currently not
* definable for agg) */
false, /* isLeakProof */
! isStrict, /* isStrict (needed for ordered set funcs) */
PROVOLATILE_IMMUTABLE, /* volatility (not
* needed for agg) */
parameterTypes, /* paramTypes */
***************
*** 273,279 **** AggregateCreate(const char *aggName,
--- 469,478 ----
values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
+ values[Anum_pg_aggregate_aggtranssortop - 1] = ObjectIdGetDatum(transsortop);
values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
+ values[Anum_pg_aggregate_aggordnargs - 1] = Int32GetDatum(numDirectArgs);
+ values[Anum_pg_aggregate_aggisordsetfunc - 1] = BoolGetDatum(isOrderedSet);
values[Anum_pg_aggregate_aggtransspace - 1] = Int32GetDatum(aggTransSpace);
if (agginitval)
values[Anum_pg_aggregate_agginitval - 1] = CStringGetTextDatum(agginitval);
***************
*** 292,309 **** AggregateCreate(const char *aggName,
/*
* Create dependencies for the aggregate (above and beyond those already
! * made by ProcedureCreate). Note: we don't need an explicit dependency
! * on aggTransType since we depend on it indirectly through transfn.
*/
myself.classId = ProcedureRelationId;
myself.objectId = procOid;
myself.objectSubId = 0;
/* Depends on transition function */
! referenced.classId = ProcedureRelationId;
! referenced.objectId = transfn;
! referenced.objectSubId = 0;
! recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
/* Depends on final function, if any */
if (OidIsValid(finalfn))
--- 491,513 ----
/*
* Create dependencies for the aggregate (above and beyond those already
! * made by ProcedureCreate). Normal aggs don't need an explicit
! * dependency on aggTransType since we depend on it indirectly through
! * transfn, but ordered set functions with variadic "any" do need one
! * (ordered set functions without variadic depend on it via the finalfn).
*/
myself.classId = ProcedureRelationId;
myself.objectId = procOid;
myself.objectSubId = 0;
/* Depends on transition function */
! if (OidIsValid(transfn))
! {
! referenced.classId = ProcedureRelationId;
! referenced.objectId = transfn;
! referenced.objectSubId = 0;
! recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
! }
/* Depends on final function, if any */
if (OidIsValid(finalfn))
***************
*** 323,328 **** AggregateCreate(const char *aggName,
--- 527,550 ----
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
+ /* Depends on transsort operator, if any */
+ if (OidIsValid(transsortop))
+ {
+ referenced.classId = OperatorRelationId;
+ referenced.objectId = transsortop;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /* May depend on aggTransType if any */
+ if (OidIsValid(aggTransType) && isOrderedSet && variadic_type == ANYOID)
+ {
+ referenced.classId = TypeRelationId;
+ referenced.objectId = aggTransType;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
return procOid;
}
*** a/src/backend/commands/aggregatecmds.c
--- b/src/backend/commands/aggregatecmds.c
***************
*** 44,52 ****
* DefineAggregate
*
* "oldstyle" signals the old (pre-8.2) style where the aggregate input type
! * is specified by a BASETYPE element in the parameters. Otherwise,
! * "args" is a list of FunctionParameter structs defining the agg's arguments.
! * "parameters" is a list of DefElem representing the agg's definition clauses.
*/
Oid
DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
--- 44,55 ----
* DefineAggregate
*
* "oldstyle" signals the old (pre-8.2) style where the aggregate input type
! * is specified by a BASETYPE element in the parameters. Otherwise, "args" is
! * a pair, whose first element is a list of FunctionParameter structs defining
! * the agg's arguments (both direct and ordered), and whose second element is
! * an Integer node with the number of direct args, or -1 if this isn't an
! * ordered set func. "parameters" is a list of DefElem representing the agg's
! * definition clauses.
*/
Oid
DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
***************
*** 58,76 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
List *transfuncName = NIL;
List *finalfuncName = NIL;
List *sortoperatorName = NIL;
TypeName *baseType = NULL;
TypeName *transType = NULL;
int32 transSpace = 0;
char *initval = NULL;
int numArgs;
oidvector *parameterTypes;
ArrayType *allParameterTypes;
ArrayType *parameterModes;
ArrayType *parameterNames;
List *parameterDefaults;
- Oid transTypeId;
char transTypeType;
ListCell *pl;
/* Convert list of names to a name and namespace */
aggNamespace = QualifiedNameGetCreationNamespace(name, &aggName);
--- 61,84 ----
List *transfuncName = NIL;
List *finalfuncName = NIL;
List *sortoperatorName = NIL;
+ List *transsortoperatorName = NIL;
TypeName *baseType = NULL;
TypeName *transType = NULL;
int32 transSpace = 0;
char *initval = NULL;
int numArgs;
+ int numDirectArgs = -1;
+ Oid transTypeId = InvalidOid;
oidvector *parameterTypes;
ArrayType *allParameterTypes;
ArrayType *parameterModes;
ArrayType *parameterNames;
List *parameterDefaults;
char transTypeType;
ListCell *pl;
+ bool ishypothetical = false;
+ bool isOrderedSet = false;
+ bool isStrict = false;
/* Convert list of names to a name and namespace */
aggNamespace = QualifiedNameGetCreationNamespace(name, &aggName);
***************
*** 81,86 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
--- 89,102 ----
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
get_namespace_name(aggNamespace));
+ Assert(args == NIL || list_length(args) == 2);
+
+ if (list_length(args) == 2)
+ {
+ numDirectArgs = intVal(lsecond(args));
+ isOrderedSet = (numDirectArgs != -1);
+ }
+
foreach(pl, parameters)
{
DefElem *defel = (DefElem *) lfirst(pl);
***************
*** 109,114 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
--- 125,136 ----
initval = defGetString(defel);
else if (pg_strcasecmp(defel->defname, "initcond1") == 0)
initval = defGetString(defel);
+ else if (pg_strcasecmp(defel->defname, "hypothetical") == 0)
+ ishypothetical = true;
+ else if (pg_strcasecmp(defel->defname, "strict") == 0)
+ isStrict = true;
+ else if (pg_strcasecmp(defel->defname, "transsortop") == 0)
+ transsortoperatorName = defGetQualifiedName(defel);
else
ereport(WARNING,
(errcode(ERRCODE_SYNTAX_ERROR),
***************
*** 116,132 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
defel->defname)));
}
! /*
! * make sure we have our required definitions
! */
! if (transType == NULL)
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! errmsg("aggregate stype must be specified")));
! if (transfuncName == NIL)
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! errmsg("aggregate sfunc must be specified")));
/*
* look up the aggregate's input datatype(s).
--- 138,172 ----
defel->defname)));
}
! if (!isOrderedSet)
! {
! /*
! * make sure we have our required definitions
! */
! if (transType == NULL)
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! errmsg("aggregate stype must be specified")));
! if (transfuncName == NIL)
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! errmsg("aggregate sfunc must be specified")));
! if (isStrict)
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! errmsg("aggregate with sfunc must not be explicitly declared STRICT")));
! }
! else
! {
! if (transfuncName != NIL)
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! errmsg("sfunc must not be specified for ordered set functions")));
! if (finalfuncName == NIL)
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! errmsg("finalfunc must be specified for ordered set functions")));
! }
/*
* look up the aggregate's input datatype(s).
***************
*** 176,183 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("basetype is redundant with aggregate input type specification")));
! numArgs = list_length(args);
! interpret_function_parameter_list(args,
InvalidOid,
true, /* is an aggregate */
queryString,
--- 216,230 ----
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("basetype is redundant with aggregate input type specification")));
! /*
! * The grammar has already concatenated the direct and ordered
! * args (if any) for us. Note that error checking for position
! * and number of VARIADIC args is not done for us, we have to
! * do it ourselves later (in AggregateCreate)
! */
!
! numArgs = list_length(linitial(args));
! interpret_function_parameter_list(linitial(args),
InvalidOid,
true, /* is an aggregate */
queryString,
***************
*** 194,200 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
}
/*
! * look up the aggregate's transtype.
*
* transtype can't be a pseudo-type, since we need to be able to store
* values of the transtype. However, we can allow polymorphic transtype
--- 241,247 ----
}
/*
! * look up the aggregate's transtype, if specified.
*
* transtype can't be a pseudo-type, since we need to be able to store
* values of the transtype. However, we can allow polymorphic transtype
***************
*** 204,221 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
* worse) by connecting up incompatible internal-using functions in an
* aggregate.
*/
! transTypeId = typenameTypeId(NULL, transType);
! transTypeType = get_typtype(transTypeId);
! if (transTypeType == TYPTYPE_PSEUDO &&
! !IsPolymorphicType(transTypeId))
{
! if (transTypeId == INTERNALOID && superuser())
! /* okay */ ;
! else
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! errmsg("aggregate transition data type cannot be %s",
! format_type_be(transTypeId))));
}
/*
--- 251,270 ----
* worse) by connecting up incompatible internal-using functions in an
* aggregate.
*/
! if (transType)
{
! transTypeId = typenameTypeId(NULL, transType);
! transTypeType = get_typtype(transTypeId);
! if (transTypeType == TYPTYPE_PSEUDO &&
! !IsPolymorphicType(transTypeId))
! {
! if (transTypeId != INTERNALOID || !superuser() || isOrderedSet)
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! errmsg("aggregate transition data type cannot be %s",
! format_type_be(transTypeId))));
! }
!
}
/*
***************
*** 227,239 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
* value. However, if it's an incorrect value it seems much more
* user-friendly to complain at CREATE AGGREGATE time.
*/
! if (initval && transTypeType != TYPTYPE_PSEUDO)
{
! Oid typinput,
! typioparam;
! getTypeInputInfo(transTypeId, &typinput, &typioparam);
! (void) OidInputFunctionCall(typinput, initval, typioparam, -1);
}
/*
--- 276,298 ----
* value. However, if it's an incorrect value it seems much more
* user-friendly to complain at CREATE AGGREGATE time.
*/
! if (transType)
{
! if (initval && transTypeType != TYPTYPE_PSEUDO)
! {
! Oid typinput,
! typioparam;
! getTypeInputInfo(transTypeId, &typinput, &typioparam);
! (void) OidInputFunctionCall(typinput, initval, typioparam, -1);
! }
! }
! else
! {
! if (initval)
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
! errmsg("INITVAL must not be specified without STYPE")));
}
/*
***************
*** 242,247 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
--- 301,307 ----
return AggregateCreate(aggName, /* aggregate name */
aggNamespace, /* namespace */
numArgs,
+ numDirectArgs,
parameterTypes,
PointerGetDatum(allParameterTypes),
PointerGetDatum(parameterModes),
***************
*** 250,256 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
transfuncName, /* step function name */
finalfuncName, /* final function name */
sortoperatorName, /* sort operator name */
transTypeId, /* transition data type */
transSpace, /* transition space */
! initval); /* initial condition */
}
--- 310,320 ----
transfuncName, /* step function name */
finalfuncName, /* final function name */
sortoperatorName, /* sort operator name */
+ transsortoperatorName, /* transsort operator name */
transTypeId, /* transition data type */
transSpace, /* transition space */
! initval, /* initial condition */
! isStrict, /* is explicitly STRICT */
! isOrderedSet, /* If the function is an ordered set */
! ishypothetical); /* If the function is a hypothetical set */
}
*** a/src/backend/commands/functioncmds.c
--- b/src/backend/commands/functioncmds.c
***************
*** 274,281 **** interpret_function_parameter_list(List *parameters,
/* handle input parameters */
if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
{
! /* other input parameters can't follow a VARIADIC parameter */
! if (varCount > 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("VARIADIC parameter must be the last input parameter")));
--- 274,286 ----
/* handle input parameters */
if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
{
! /*
! * For functions, other input parameters can't follow a VARIADIC
! * parameter; for aggregates, we might be dealing with an ordered
! * set function which have more complex rules for variadics, so
! * punt the error checking for that case to the caller.
! */
! if (varCount > 0 && !is_aggregate)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("VARIADIC parameter must be the last input parameter")));
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
***************
*** 4410,4415 **** ExecInitExpr(Expr *node, PlanState *parent)
--- 4410,4416 ----
astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
parent);
+ astate->orddirectargs = (List *) ExecInitExpr((Expr *) aggref->orddirectargs, parent);
astate->aggfilter = ExecInitExpr(aggref->aggfilter,
parent);
*** a/src/backend/executor/functions.c
--- b/src/backend/executor/functions.c
***************
*** 380,387 **** sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
param = ParseFuncOrColumn(pstate,
list_make1(subfield),
list_make1(param),
! NIL, NULL, false, false, false,
! NULL, true, cref->location);
}
return param;
--- 380,387 ----
param = ParseFuncOrColumn(pstate,
list_make1(subfield),
list_make1(param),
! cref->location,
! NULL);
}
return param;
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
***************
*** 3,9 ****
* nodeAgg.c
* Routines to handle aggregate nodes.
*
! * ExecAgg evaluates each aggregate in the following steps:
*
* transvalue = initcond
* foreach input_tuple do
--- 3,9 ----
* nodeAgg.c
* Routines to handle aggregate nodes.
*
! * ExecAgg evaluates each normal aggregate in the following steps:
*
* transvalue = initcond
* foreach input_tuple do
***************
*** 66,71 ****
--- 66,91 ----
* AggState is available as context in earlier releases (back to 8.1),
* but direct examination of the node is needed to use it before 9.0.
*
+ *---
+ *
+ * Ordered set functions modify the above process in a number of ways.
+ * Most importantly, they do not have transfuncs at all; the same sort
+ * mechanism used for ORDER BY/DISTINCT as described above is used to
+ * process the input, but then the finalfunc is called without actually
+ * running the sort (the finalfunc is allowed to insert rows first).
+ * The finalfunc has access via a set of AggSet* API functions to the
+ * Tuplesortstate, row count in the group, and other ancillary info.
+ *
+ * Ordered set functions can, however, have a transvalue declared; this is
+ * treated as a constant, and added to the end of the sort fields.
+ * Hypothetical set functions use this to provide a flag that distinguishes
+ * the hypothetical row from the input data.
+ *
+ * Since they have no transfunc, ordered set functions have their own
+ * 'strict' flag stored in the aggregate's own pg_proc entry; this affects
+ * whether rows containing nulls are placed in the sorter. But since we
+ * pass dummy null arguments to the finalfunc for type resolution purposes,
+ * no ordered set finalfunc is allowed to be strict.
*
* Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
***************
*** 87,96 ****
--- 107,118 ----
#include "executor/nodeAgg.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+ #include "nodes/makefuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/tlist.h"
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
+ #include "parser/parse_clause.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
***************
*** 105,110 ****
--- 127,134 ----
*/
typedef struct AggStatePerAggData
{
+ NodeTag type;
+
/*
* These values are set up during ExecInitAgg() and do not change
* thereafter:
***************
*** 114,123 **** typedef struct AggStatePerAggData
AggrefExprState *aggrefstate;
Aggref *aggref;
! /* number of input arguments for aggregate function proper */
int numArguments;
! /* number of inputs including ORDER BY expressions */
int numInputs;
/* Oids of transfer functions */
--- 138,162 ----
AggrefExprState *aggrefstate;
Aggref *aggref;
! /* Pointer to parent AggState node */
! AggState *aggstate;
!
! /* copied from aggref */
! bool isOrderedSet;
!
! /*
! * number of arguments for aggregate function proper.
! * For ordered set functions, this includes the ORDER BY
! * columns, *except* in the case of hypothetical set functions.
! */
int numArguments;
! /*
! * number of inputs including ORDER BY expressions. For ordered
! * set functions, *only* the ORDER BY expressions are included
! * here, since the direct args to the function are not properly
! * "input" in the sense of being derived from the tuple group.
! */
int numInputs;
/* Oids of transfer functions */
***************
*** 126,137 **** typedef struct AggStatePerAggData
/*
* fmgr lookup data for transfer functions --- only valid when
! * corresponding oid is not InvalidOid. Note in particular that fn_strict
! * flags are kept here.
*/
FmgrInfo transfn;
FmgrInfo finalfn;
/* Input collation derived for aggregate */
Oid aggCollation;
--- 165,187 ----
/*
* fmgr lookup data for transfer functions --- only valid when
! * corresponding oid is not InvalidOid.
*/
FmgrInfo transfn;
FmgrInfo finalfn;
+ /*
+ * If >0, aggregate as a whole is strict (skips null input)
+ * The value specifies how many columns to check; normal aggs
+ * only check numArguments, while ordered set functions check
+ * numInputs.
+ *
+ * Ordered set functions are not allowed to have strict finalfns;
+ * other aggregates respect the finalfn strict flag in the
+ * FmgrInfo above.
+ */
+ int numStrict;
+
/* Input collation derived for aggregate */
Oid aggCollation;
***************
*** 148,153 **** typedef struct AggStatePerAggData
--- 198,206 ----
Oid *sortCollations;
bool *sortNullsFirst;
+ /* just for convenience of ordered set funcs, not used here */
+ Oid *sortEqOperators;
+
/*
* fmgr lookup data for input columns' equality operators --- only
* set/used when aggregate has DISTINCT flag. Note that these are in
***************
*** 204,209 **** typedef struct AggStatePerAggData
--- 257,265 ----
*/
Tuplesortstate *sortstate; /* sort object, if DISTINCT or ORDER BY */
+
+ int64 number_of_rows; /* number of rows */
+
} AggStatePerAggData;
/*
***************
*** 300,305 **** initialize_aggregates(AggState *aggstate,
--- 356,363 ----
AggStatePerAgg peraggstate = &peragg[aggno];
AggStatePerGroup pergroupstate = &pergroup[aggno];
+ peraggstate->number_of_rows = 0;
+
/*
* Start a fresh sort operation for each DISTINCT/ORDER BY aggregate.
*/
***************
*** 383,396 **** advance_transition_function(AggState *aggstate,
MemoryContext oldContext;
Datum newVal;
int i;
! if (peraggstate->transfn.fn_strict)
{
/*
* For a strict transfn, nothing happens when there's a NULL input; we
* just keep the prior transValue.
*/
! for (i = 1; i <= numArguments; i++)
{
if (fcinfo->argnull[i])
return;
--- 441,457 ----
MemoryContext oldContext;
Datum newVal;
int i;
+ int numStrict = peraggstate->numStrict;
! Assert(OidIsValid(peraggstate->transfn_oid));
!
! if (numStrict > 0)
{
/*
* For a strict transfn, nothing happens when there's a NULL input; we
* just keep the prior transValue.
*/
! for (i = 1; i <= numStrict; i++)
{
if (fcinfo->argnull[i])
return;
***************
*** 506,529 **** advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
if (peraggstate->numSortCols > 0)
{
/* DISTINCT and/or ORDER BY case */
Assert(slot->tts_nvalid == peraggstate->numInputs);
/*
! * If the transfn is strict, we want to check for nullity before
* storing the row in the sorter, to save space if there are a lot
! * of nulls. Note that we must only check numArguments columns,
! * not numInputs, since nullity in columns used only for sorting
! * is not relevant here.
*/
! if (peraggstate->transfn.fn_strict)
{
! for (i = 0; i < nargs; i++)
{
if (slot->tts_isnull[i])
break;
}
! if (i < nargs)
continue;
}
--- 567,590 ----
if (peraggstate->numSortCols > 0)
{
+ int numStrict = peraggstate->numStrict;
+
/* DISTINCT and/or ORDER BY case */
Assert(slot->tts_nvalid == peraggstate->numInputs);
/*
! * If the aggregate is strict, we want to check for nullity before
* storing the row in the sorter, to save space if there are a lot
! * of nulls.
*/
! if (numStrict > 0)
{
! for (i = 0; i < numStrict; i++)
{
if (slot->tts_isnull[i])
break;
}
! if (i < numStrict)
continue;
}
***************
*** 534,539 **** advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
--- 595,602 ----
slot->tts_isnull[0]);
else
tuplesort_puttupleslot(peraggstate->sortstate, slot);
+
+ peraggstate->number_of_rows++;
}
else
{
***************
*** 756,770 **** finalize_aggregate(AggState *aggstate,
if (OidIsValid(peraggstate->finalfn_oid))
{
FunctionCallInfoData fcinfo;
! InitFunctionCallInfoData(fcinfo, &(peraggstate->finalfn), 1,
! peraggstate->aggCollation,
! (void *) aggstate, NULL);
! fcinfo.arg[0] = pergroupstate->transValue;
! fcinfo.argnull[0] = pergroupstate->transValueIsNull;
! if (fcinfo.flinfo->fn_strict && pergroupstate->transValueIsNull)
{
! /* don't call a strict function with NULL inputs */
*resultVal = (Datum) 0;
*resultIsNull = true;
}
--- 819,884 ----
if (OidIsValid(peraggstate->finalfn_oid))
{
FunctionCallInfoData fcinfo;
+ bool isnull = false;
+
+ if (!(peraggstate->isOrderedSet))
+ {
+ InitFunctionCallInfoData(fcinfo,
+ &(peraggstate->finalfn),
+ 1,
+ peraggstate->aggCollation,
+ (void *) aggstate,
+ NULL);
+
+ fcinfo.arg[0] = pergroupstate->transValue;
+ fcinfo.argnull[0] = isnull = pergroupstate->transValueIsNull;
+ }
+ else
+ {
+ List *args = peraggstate->aggrefstate->orddirectargs;
+ ListCell *lc;
+ int i = 0;
+ int numArguments = peraggstate->numArguments;
+
+ ExecClearTuple(peraggstate->evalslot);
+ ExecClearTuple(peraggstate->uniqslot);
+
+ InitFunctionCallInfoData(fcinfo,
+ &(peraggstate->finalfn),
+ peraggstate->numArguments,
+ peraggstate->aggCollation,
+ (void *) peraggstate,
+ NULL);
+
+ foreach (lc, args)
+ {
+ ExprState *expr = (ExprState *) lfirst(lc);
+
+ fcinfo.arg[i] = ExecEvalExpr(expr,
+ aggstate->ss.ps.ps_ExprContext,
+ &fcinfo.argnull[i],
+ NULL);
+ if (fcinfo.argnull[i])
+ isnull = true;
! ++i;
! }
!
! for(; i < numArguments; i++)
! {
! fcinfo.arg[i] = (Datum) 0;
! fcinfo.argnull[i] = true;
! isnull = true;
! }
! }
!
! if (isnull && fcinfo.flinfo->fn_strict)
{
! /*
! * don't call a strict function with NULL inputs; for ordered set
! * functions this is paranoia, we already required that fn_strict
! * is false, but easy to check anyway
! */
*resultVal = (Datum) 0;
*resultIsNull = true;
}
***************
*** 1164,1169 **** agg_retrieve_direct(AggState *aggstate)
--- 1278,1294 ----
}
/*
+ * Use the representative input tuple for any references to
+ * non-aggregated input columns in the qual and tlist. (If we are not
+ * grouping, and there are no input rows at all, we will come here
+ * with an empty firstSlot ... but if not grouping, there can't be any
+ * references to non-aggregated input columns, so no problem.)
+ * We do this before finalizing because for ordered set functions,
+ * finalize_aggregates can evaluate arguments referencing the tuple.
+ */
+ econtext->ecxt_outertuple = firstSlot;
+
+ /*
* Done scanning input tuple group. Finalize each aggregate
* calculation, and stash results in the per-output-tuple context.
*/
***************
*** 1174,1187 **** agg_retrieve_direct(AggState *aggstate)
if (peraggstate->numSortCols > 0)
{
! if (peraggstate->numInputs == 1)
! process_ordered_aggregate_single(aggstate,
! peraggstate,
! pergroupstate);
! else
! process_ordered_aggregate_multi(aggstate,
! peraggstate,
! pergroupstate);
}
finalize_aggregate(aggstate, peraggstate, pergroupstate,
--- 1299,1315 ----
if (peraggstate->numSortCols > 0)
{
! if (!(peraggstate->isOrderedSet))
! {
! if (peraggstate->numInputs == 1)
! process_ordered_aggregate_single(aggstate,
! peraggstate,
! pergroupstate);
! else
! process_ordered_aggregate_multi(aggstate,
! peraggstate,
! pergroupstate);
! }
}
finalize_aggregate(aggstate, peraggstate, pergroupstate,
***************
*** 1189,1203 **** agg_retrieve_direct(AggState *aggstate)
}
/*
- * Use the representative input tuple for any references to
- * non-aggregated input columns in the qual and tlist. (If we are not
- * grouping, and there are no input rows at all, we will come here
- * with an empty firstSlot ... but if not grouping, there can't be any
- * references to non-aggregated input columns, so no problem.)
- */
- econtext->ecxt_outertuple = firstSlot;
-
- /*
* Check the qual (HAVING clause); if the group does not match, ignore
* it and loop back to try to process another group.
*/
--- 1317,1322 ----
***************
*** 1568,1577 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
--- 1687,1698 ----
int numInputs;
int numSortCols;
int numDistinctCols;
+ bool isOrderedSet = aggref->isordset;
List *sortlist;
HeapTuple aggTuple;
Form_pg_aggregate aggform;
Oid aggtranstype;
+ Oid aggtranstypecoll;
AclResult aclresult;
Oid transfn_oid,
finalfn_oid;
***************
*** 1580,1585 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
--- 1701,1710 ----
Datum textInitVal;
int i;
ListCell *lc;
+ bool is_strict;
+ Oid inputCollations[FUNC_MAX_ARGS];
+ List *argexprs;
+ List *argexprstate;
/* Planner should have assigned aggregate to correct level */
Assert(aggref->agglevelsup == 0);
***************
*** 1601,1631 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
/* Nope, so assign a new PerAgg record */
peraggstate = &peragg[++aggno];
- /* Mark Aggref state node with assigned index in the result array */
- aggrefstate->aggno = aggno;
-
/* Fill in the peraggstate data */
! peraggstate->aggrefstate = aggrefstate;
peraggstate->aggref = aggref;
! numInputs = list_length(aggref->args);
! peraggstate->numInputs = numInputs;
! peraggstate->sortstate = NULL;
! /*
! * Get actual datatypes of the inputs. These could be different from
! * the agg's declared input types, when the agg accepts ANY or a
! * polymorphic type.
! */
! numArguments = 0;
! foreach(lc, aggref->args)
! {
! TargetEntry *tle = (TargetEntry *) lfirst(lc);
! if (!tle->resjunk)
! inputTypes[numArguments++] = exprType((Node *) tle->expr);
! }
! peraggstate->numArguments = numArguments;
aggTuple = SearchSysCache1(AGGFNOID,
ObjectIdGetDatum(aggref->aggfnoid));
if (!HeapTupleIsValid(aggTuple))
--- 1726,1743 ----
/* Nope, so assign a new PerAgg record */
peraggstate = &peragg[++aggno];
/* Fill in the peraggstate data */
! peraggstate->type = T_AggStatePerAggData;
! peraggstate->aggstate = aggstate;
peraggstate->aggref = aggref;
! peraggstate->aggrefstate = aggrefstate;
! peraggstate->isOrderedSet = isOrderedSet;
! /* Mark Aggref state node with assigned index in the result array */
! aggrefstate->aggno = aggno;
+ /* Fetch the pg_aggregate row */
aggTuple = SearchSysCache1(AGGFNOID,
ObjectIdGetDatum(aggref->aggfnoid));
if (!HeapTupleIsValid(aggTuple))
***************
*** 1633,1638 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
--- 1745,1757 ----
aggref->aggfnoid);
aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
+ /*
+ * Check that the definition hasn't somehow changed incompatibly.
+ */
+ if (isOrderedSet != (aggform->aggisordsetfunc)
+ || (aggref->ishypothetical != (aggform->aggordnargs == -2)))
+ elog(ERROR, "incompatible change to aggregate definition");
+
/* Check permission to call aggregate function */
aclresult = pg_proc_aclcheck(aggref->aggfnoid, GetUserId(),
ACL_EXECUTE);
***************
*** 1644,1668 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
! /* Check that aggregate owner has permission to call component fns */
{
HeapTuple procTuple;
Oid aggOwner;
procTuple = SearchSysCache1(PROCOID,
ObjectIdGetDatum(aggref->aggfnoid));
if (!HeapTupleIsValid(procTuple))
elog(ERROR, "cache lookup failed for function %u",
aggref->aggfnoid);
! aggOwner = ((Form_pg_proc) GETSTRUCT(procTuple))->proowner;
ReleaseSysCache(procTuple);
! aclresult = pg_proc_aclcheck(transfn_oid, aggOwner,
! ACL_EXECUTE);
! if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_PROC,
get_func_name(transfn_oid));
! InvokeFunctionExecuteHook(transfn_oid);
if (OidIsValid(finalfn_oid))
{
aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
--- 1763,1799 ----
peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
! /*
! * Check that aggregate owner has permission to call component fns
! * In passing, fetch the proisstrict flag for the aggregate proper,
! * which subs for the transfn's strictness flag in cases where there
! * is no transfn.
! */
{
HeapTuple procTuple;
Oid aggOwner;
+ Form_pg_proc procp;
procTuple = SearchSysCache1(PROCOID,
ObjectIdGetDatum(aggref->aggfnoid));
if (!HeapTupleIsValid(procTuple))
elog(ERROR, "cache lookup failed for function %u",
aggref->aggfnoid);
! procp = (Form_pg_proc) GETSTRUCT(procTuple);
! aggOwner = procp->proowner;
! is_strict = procp->proisstrict;
ReleaseSysCache(procTuple);
! if (OidIsValid(transfn_oid))
! {
! aclresult = pg_proc_aclcheck(transfn_oid, aggOwner,
! ACL_EXECUTE);
! if (aclresult != ACLCHECK_OK && OidIsValid(transfn_oid))
aclcheck_error(aclresult, ACL_KIND_PROC,
get_func_name(transfn_oid));
! InvokeFunctionExecuteHook(transfn_oid);
! }
!
if (OidIsValid(finalfn_oid))
{
aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
***************
*** 1674,1690 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
}
}
/* resolve actual type of transition state, if polymorphic */
aggtranstype = aggform->aggtranstype;
! if (IsPolymorphicType(aggtranstype))
{
/* have to fetch the agg's declared input types... */
Oid *declaredArgTypes;
! int agg_nargs;
(void) get_func_signature(aggref->aggfnoid,
! &declaredArgTypes, &agg_nargs);
! Assert(agg_nargs == numArguments);
aggtranstype = enforce_generic_type_consistency(inputTypes,
declaredArgTypes,
agg_nargs,
--- 1805,1841 ----
}
}
+ /*
+ * Get actual datatypes of the inputs. These could be different from
+ * the agg's declared input types, when the agg accepts ANY or a
+ * polymorphic type.
+ */
+
+ peraggstate->numInputs = numInputs = list_length(aggref->args);
+
+ numArguments = get_aggregate_argtypes(aggref,
+ inputTypes,
+ inputCollations);
+
/* resolve actual type of transition state, if polymorphic */
aggtranstype = aggform->aggtranstype;
! if (OidIsValid(aggtranstype) && IsPolymorphicType(aggtranstype))
{
/* have to fetch the agg's declared input types... */
Oid *declaredArgTypes;
! int agg_nargs;
(void) get_func_signature(aggref->aggfnoid,
! &declaredArgTypes,
! &agg_nargs);
!
! /*
! * if variadic "any", might be more actual args than declared
! * args, but these extra args can't influence the determination
! * of polymorphic transition or result type.
! */
! Assert(agg_nargs <= numArguments);
!
aggtranstype = enforce_generic_type_consistency(inputTypes,
declaredArgTypes,
agg_nargs,
***************
*** 1693,1727 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
pfree(declaredArgTypes);
}
/* build expression trees using actual argument & result types */
! build_aggregate_fnexprs(inputTypes,
! numArguments,
! aggref->aggvariadic,
! aggtranstype,
! aggref->aggtype,
! aggref->inputcollid,
! transfn_oid,
! finalfn_oid,
! &transfnexpr,
! &finalfnexpr);
!
! fmgr_info(transfn_oid, &peraggstate->transfn);
! fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
if (OidIsValid(finalfn_oid))
{
fmgr_info(finalfn_oid, &peraggstate->finalfn);
fmgr_info_set_expr((Node *) finalfnexpr, &peraggstate->finalfn);
}
peraggstate->aggCollation = aggref->inputcollid;
get_typlenbyval(aggref->aggtype,
&peraggstate->resulttypeLen,
&peraggstate->resulttypeByVal);
! get_typlenbyval(aggtranstype,
! &peraggstate->transtypeLen,
! &peraggstate->transtypeByVal);
/*
* initval is potentially null, so don't try to access it as a struct
--- 1844,1925 ----
pfree(declaredArgTypes);
}
+ aggtranstypecoll = get_typcollation(aggtranstype);
+
/* build expression trees using actual argument & result types */
!
! if (!isOrderedSet)
! {
! build_aggregate_fnexprs(inputTypes,
! numArguments,
! aggref->aggvariadic,
! aggtranstype,
! aggref->aggtype,
! aggref->inputcollid,
! transfn_oid,
! finalfn_oid,
! &transfnexpr,
! &finalfnexpr);
! }
! else
! {
! /*
! * The transvalue counts as an argument, but not for hypothetical
! * set funcs.
! */
! if (OidIsValid(aggtranstype) && !(aggref->ishypothetical))
! {
! if (numArguments == FUNC_MAX_ARGS)
! elog(ERROR, "too many arguments to ordered set function");
!
! inputTypes[numArguments++] = aggtranstype;
! inputCollations[numArguments++] = aggtranstypecoll;
! }
!
! build_orderedset_fnexprs(inputTypes,
! numArguments,
! aggref->aggvariadic,
! aggref->aggtype,
! aggref->inputcollid,
! inputCollations,
! finalfn_oid,
! &finalfnexpr);
! }
!
! peraggstate->numArguments = numArguments;
!
! if (OidIsValid(transfn_oid))
! {
! fmgr_info(transfn_oid, &peraggstate->transfn);
! fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
!
! is_strict = peraggstate->transfn.fn_strict;
! }
if (OidIsValid(finalfn_oid))
{
fmgr_info(finalfn_oid, &peraggstate->finalfn);
fmgr_info_set_expr((Node *) finalfnexpr, &peraggstate->finalfn);
+ if (peraggstate->finalfn.fn_strict && isOrderedSet)
+ elog(ERROR, "ordered set finalfns must not be strict");
}
+ if (is_strict)
+ peraggstate->numStrict = (isOrderedSet ? numInputs : numArguments);
+ else
+ peraggstate->numStrict = 0;
+
peraggstate->aggCollation = aggref->inputcollid;
get_typlenbyval(aggref->aggtype,
&peraggstate->resulttypeLen,
&peraggstate->resulttypeByVal);
! if (OidIsValid(aggtranstype))
! {
! get_typlenbyval(aggtranstype,
! &peraggstate->transtypeLen,
! &peraggstate->transtypeByVal);
! }
/*
* initval is potentially null, so don't try to access it as a struct
***************
*** 1744,1750 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
* transValue. This should have been checked at agg definition time,
* but just in case...
*/
! if (peraggstate->transfn.fn_strict && peraggstate->initValueIsNull)
{
if (numArguments < 1 ||
!IsBinaryCoercible(inputTypes[0], aggtranstype))
--- 1942,1950 ----
* transValue. This should have been checked at agg definition time,
* but just in case...
*/
! if (OidIsValid(peraggstate->transfn_oid)
! && peraggstate->transfn.fn_strict
! && peraggstate->initValueIsNull)
{
if (numArguments < 1 ||
!IsBinaryCoercible(inputTypes[0], aggtranstype))
***************
*** 1754,1774 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
aggref->aggfnoid)));
}
! /*
! * Get a tupledesc corresponding to the inputs (including sort
! * expressions) of the agg.
! */
! peraggstate->evaldesc = ExecTypeFromTL(aggref->args, false);
!
! /* Create slot we're going to do argument evaluation in */
! peraggstate->evalslot = ExecInitExtraTupleSlot(estate);
! ExecSetSlotDescriptor(peraggstate->evalslot, peraggstate->evaldesc);
!
! /* Set up projection info for evaluation */
! peraggstate->evalproj = ExecBuildProjectionInfo(aggrefstate->args,
! aggstate->tmpcontext,
! peraggstate->evalslot,
! NULL);
/*
* If we're doing either DISTINCT or ORDER BY, then we have a list of
--- 1954,1961 ----
aggref->aggfnoid)));
}
! argexprs = aggref->args;
! argexprstate = aggrefstate->args;
/*
* If we're doing either DISTINCT or ORDER BY, then we have a list of
***************
*** 1777,1782 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
--- 1964,1974 ----
*
* Note that by construction, if there is a DISTINCT clause then the
* ORDER BY clause is a prefix of it (see transformDistinctClause).
+ *
+ * If we're doing an ordered set function, though, we want to do the
+ * initialization for DISTINCT since the ordered set finalfn might
+ * want it, and it's much easier to do it here. So set numDistinctCols
+ * and let the later initialization take care of it.
*/
if (aggref->aggdistinct)
{
***************
*** 1788,1798 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
{
sortlist = aggref->aggorder;
numSortCols = list_length(sortlist);
! numDistinctCols = 0;
}
peraggstate->numSortCols = numSortCols;
peraggstate->numDistinctCols = numDistinctCols;
if (numSortCols > 0)
{
--- 1980,2065 ----
{
sortlist = aggref->aggorder;
numSortCols = list_length(sortlist);
! numDistinctCols = isOrderedSet ? numSortCols : 0;
! }
!
! /*
! * If this is an ordered set function, and we have a transtype, then
! * it represents an extra column to be added to the sorter with a
! * fixed value. Plus, if aggtranssortop is valid, we have to include
! * a sort entry for the new column.
! *
! * I'd probably have done this in the planner if I'd seen any
! * possible place to put it; if there is one, it's very obscure.
! */
!
! if (OidIsValid(aggtranstype) && isOrderedSet)
! {
! Oid sortop = aggform->aggtranssortop;
! Const *node = makeNode(Const);
! TargetEntry *tle;
! SortGroupClause *sortcl = NULL;
!
! node->consttype = aggtranstype;
! node->consttypmod = -1;
! node->constcollid = aggtranstypecoll;
! node->constlen = peraggstate->transtypeLen;
! node->constvalue = peraggstate->initValue;
! node->constisnull = peraggstate->initValueIsNull;
! node->constbyval = peraggstate->transtypeByVal;
! node->location = -1;
!
! tle = makeTargetEntry((Expr *) node,
! ++numInputs,
! NULL,
! true);
!
! peraggstate->numInputs = numInputs;
!
! if (OidIsValid(sortop))
! {
! Assert(aggref->aggdistinct == NIL);
!
! sortcl = makeNode(SortGroupClause);
!
! sortcl->tleSortGroupRef = assignSortGroupRef(tle, argexprs);
!
! sortcl->sortop = sortop;
! sortcl->hashable = false;
! sortcl->eqop = get_equality_op_for_ordering_op(sortop,
! &sortcl->nulls_first);
!
! sortlist = lappend(list_copy(sortlist), sortcl);
! ++numSortCols;
! ++numDistinctCols;
! }
!
! /* shallow-copy the passed-in lists, which we must not scribble on. */
!
! argexprs = lappend(list_copy(argexprs), (Node *) tle);
! argexprstate = lappend(list_copy(argexprstate),
! ExecInitExpr((Expr *) tle, (PlanState *) aggstate));
}
peraggstate->numSortCols = numSortCols;
peraggstate->numDistinctCols = numDistinctCols;
+ peraggstate->sortstate = NULL;
+
+ /*
+ * Get a tupledesc corresponding to the inputs (including sort
+ * expressions) of the agg.
+ */
+ peraggstate->evaldesc = ExecTypeFromTL(argexprs, false);
+
+ /* Create slot we're going to do argument evaluation in */
+ peraggstate->evalslot = ExecInitExtraTupleSlot(estate);
+ ExecSetSlotDescriptor(peraggstate->evalslot, peraggstate->evaldesc);
+
+ /* Set up projection info for evaluation */
+ peraggstate->evalproj = ExecBuildProjectionInfo(argexprstate,
+ aggstate->tmpcontext,
+ peraggstate->evalslot,
+ NULL);
if (numSortCols > 0)
{
***************
*** 1805,1815 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
/* If we have only one input, we need its len/byval info. */
if (numInputs == 1)
{
! get_typlenbyval(inputTypes[0],
&peraggstate->inputtypeLen,
&peraggstate->inputtypeByVal);
}
! else if (numDistinctCols > 0)
{
/* we will need an extra slot to store prior values */
peraggstate->uniqslot = ExecInitExtraTupleSlot(estate);
--- 2072,2083 ----
/* If we have only one input, we need its len/byval info. */
if (numInputs == 1)
{
! get_typlenbyval(peraggstate->evaldesc->attrs[0]->atttypid,
&peraggstate->inputtypeLen,
&peraggstate->inputtypeByVal);
}
!
! if (numDistinctCols > 0 && (numInputs > 1 || isOrderedSet))
{
/* we will need an extra slot to store prior values */
peraggstate->uniqslot = ExecInitExtraTupleSlot(estate);
***************
*** 1822,1871 **** ExecInitAgg(Agg *node, EState *estate, int eflags)
(AttrNumber *) palloc(numSortCols * sizeof(AttrNumber));
peraggstate->sortOperators =
(Oid *) palloc(numSortCols * sizeof(Oid));
peraggstate->sortCollations =
(Oid *) palloc(numSortCols * sizeof(Oid));
peraggstate->sortNullsFirst =
(bool *) palloc(numSortCols * sizeof(bool));
i = 0;
foreach(lc, sortlist)
{
SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
TargetEntry *tle = get_sortgroupclause_tle(sortcl,
! aggref->args);
/* the parser should have made sure of this */
Assert(OidIsValid(sortcl->sortop));
peraggstate->sortColIdx[i] = tle->resno;
peraggstate->sortOperators[i] = sortcl->sortop;
peraggstate->sortCollations[i] = exprCollation((Node *) tle->expr);
peraggstate->sortNullsFirst[i] = sortcl->nulls_first;
- i++;
- }
- Assert(i == numSortCols);
- }
! if (aggref->aggdistinct)
! {
! Assert(numArguments > 0);
!
! /*
! * We need the equal function for each DISTINCT comparison we will
! * make.
! */
! peraggstate->equalfns =
! (FmgrInfo *) palloc(numDistinctCols * sizeof(FmgrInfo));
!
! i = 0;
! foreach(lc, aggref->aggdistinct)
! {
! SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
- fmgr_info(get_opcode(sortcl->eqop), &peraggstate->equalfns[i]);
i++;
}
! Assert(i == numDistinctCols);
}
ReleaseSysCache(aggTuple);
--- 2090,2136 ----
(AttrNumber *) palloc(numSortCols * sizeof(AttrNumber));
peraggstate->sortOperators =
(Oid *) palloc(numSortCols * sizeof(Oid));
+ peraggstate->sortEqOperators =
+ (Oid *) palloc(numSortCols * sizeof(Oid));
peraggstate->sortCollations =
(Oid *) palloc(numSortCols * sizeof(Oid));
peraggstate->sortNullsFirst =
(bool *) palloc(numSortCols * sizeof(bool));
+ if (numDistinctCols > 0)
+ peraggstate->equalfns =
+ (FmgrInfo *) palloc(numDistinctCols * sizeof(FmgrInfo));
+ else
+ peraggstate->equalfns = NULL;
+
i = 0;
foreach(lc, sortlist)
{
SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
TargetEntry *tle = get_sortgroupclause_tle(sortcl,
! argexprs);
/* the parser should have made sure of this */
Assert(OidIsValid(sortcl->sortop));
peraggstate->sortColIdx[i] = tle->resno;
peraggstate->sortOperators[i] = sortcl->sortop;
+ peraggstate->sortEqOperators[i] = sortcl->eqop;
peraggstate->sortCollations[i] = exprCollation((Node *) tle->expr);
peraggstate->sortNullsFirst[i] = sortcl->nulls_first;
! /*
! * It's OK to get the equalfns here too, since we already
! * require that sortlist is aggref->aggdistinct for the normal
! * distinct case, and for ordered set functions using the
! * (possibly modified copy of) aggref->aggorder is correct
! */
! if (peraggstate->equalfns)
! fmgr_info(get_opcode(sortcl->eqop), &peraggstate->equalfns[i]);
i++;
}
! Assert(i == numSortCols);
}
ReleaseSysCache(aggTuple);
***************
*** 2023,2028 **** ExecReScanAgg(AggState *node)
--- 2288,2296 ----
* If aggcontext isn't NULL, the function also stores at *aggcontext the
* identity of the memory context that aggregate transition values are
* being stored in.
+ *
+ * We do NOT include AGG_CONTEXT_ORDERED as a possible return here, since
+ * that would open a security hole.
*/
int
AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
***************
*** 2063,2065 **** aggregate_dummy(PG_FUNCTION_ARGS)
--- 2331,2448 ----
fcinfo->flinfo->fn_oid);
return (Datum) 0; /* keep compiler quiet */
}
+
+ /* AggSetGetRowCount - Get the number of rows in case of ordered set
+ * functions.
+ */
+ int64
+ AggSetGetRowCount(FunctionCallInfo fcinfo)
+ {
+ if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ {
+ return ((AggStatePerAggData *)fcinfo->context)->number_of_rows;
+ }
+
+ elog(ERROR, "Called AggSetGetRowCount on non ordered set function");
+ return -1;
+ }
+
+ /* AggSetGetSortInfo - Get the sort state in the case of
+ * ordered set functions.
+ */
+ void
+ AggSetGetSortInfo(FunctionCallInfo fcinfo,
+ Tuplesortstate **sortstate,
+ TupleDesc *tupdesc,
+ TupleTableSlot **tupslot,
+ Oid *datumtype)
+ {
+ if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ {
+ AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+
+ *sortstate = peraggstate->sortstate;
+ if (peraggstate->numInputs == 1)
+ {
+ if (tupdesc)
+ *tupdesc = NULL;
+ if (datumtype)
+ *datumtype = peraggstate->evaldesc->attrs[0]->atttypid;
+ }
+ else
+ {
+ if (tupdesc)
+ *tupdesc = peraggstate->evaldesc;
+ if (datumtype)
+ *datumtype = InvalidOid;
+ }
+
+ if (tupslot)
+ *tupslot = peraggstate->evalslot;
+ }
+ else
+ elog(ERROR, "AggSetSortInfo called on non ordered set function");
+ }
+
+ int
+ AggSetGetDistinctInfo(FunctionCallInfo fcinfo,
+ TupleTableSlot **uniqslot,
+ AttrNumber **sortColIdx,
+ FmgrInfo **equalfns)
+ {
+ if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ {
+ AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+
+ if (uniqslot)
+ *uniqslot = peraggstate->uniqslot;
+ if (sortColIdx)
+ *sortColIdx = peraggstate->sortColIdx;
+ if (equalfns)
+ *equalfns = peraggstate->equalfns;
+
+ return peraggstate->numDistinctCols;
+ }
+ else
+ elog(ERROR, "AggSetGetDistinctOperators called on non ordered set function");
+ }
+
+ int
+ AggSetGetSortOperators(FunctionCallInfo fcinfo,
+ AttrNumber **sortColIdx,
+ Oid **sortOperators,
+ Oid **sortEqOperators,
+ Oid **sortCollations,
+ bool **sortNullsFirst)
+ {
+ if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ {
+ AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+
+ if (sortColIdx)
+ *sortColIdx = peraggstate->sortColIdx;
+ if (sortOperators)
+ *sortOperators = peraggstate->sortOperators;
+ if (sortEqOperators)
+ *sortEqOperators = peraggstate->sortEqOperators;
+ if (sortCollations)
+ *sortCollations = peraggstate->sortCollations;
+ if (sortNullsFirst)
+ *sortNullsFirst = peraggstate->sortNullsFirst;
+
+ return peraggstate->numSortCols;
+ }
+ else
+ elog(ERROR, "AggSetGetSortOperators called on non ordered set function");
+ }
+
+ void
+ AggSetGetPerTupleContext(FunctionCallInfo fcinfo,
+ MemoryContext *memcontext)
+ {
+ if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData))
+ {
+ AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context;
+ *memcontext = peraggstate->aggstate->tmpcontext->ecxt_per_tuple_memory;
+ }
+ }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1139,1147 **** _copyAggref(const Aggref *from)
--- 1139,1150 ----
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(aggorder);
COPY_NODE_FIELD(aggdistinct);
+ COPY_NODE_FIELD(orddirectargs);
COPY_NODE_FIELD(aggfilter);
COPY_SCALAR_FIELD(aggstar);
COPY_SCALAR_FIELD(aggvariadic);
+ COPY_SCALAR_FIELD(isordset);
+ COPY_SCALAR_FIELD(ishypothetical);
COPY_SCALAR_FIELD(agglevelsup);
COPY_LOCATION_FIELD(location);
***************
*** 2174,2179 **** _copyFuncCall(const FuncCall *from)
--- 2177,2183 ----
COPY_SCALAR_FIELD(agg_star);
COPY_SCALAR_FIELD(agg_distinct);
COPY_SCALAR_FIELD(func_variadic);
+ COPY_SCALAR_FIELD(has_within_group);
COPY_NODE_FIELD(over);
COPY_LOCATION_FIELD(location);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 196,204 **** _equalAggref(const Aggref *a, const Aggref *b)
--- 196,207 ----
COMPARE_NODE_FIELD(args);
COMPARE_NODE_FIELD(aggorder);
COMPARE_NODE_FIELD(aggdistinct);
+ COMPARE_NODE_FIELD(orddirectargs);
COMPARE_NODE_FIELD(aggfilter);
COMPARE_SCALAR_FIELD(aggstar);
COMPARE_SCALAR_FIELD(aggvariadic);
+ COMPARE_SCALAR_FIELD(isordset);
+ COMPARE_SCALAR_FIELD(ishypothetical);
COMPARE_SCALAR_FIELD(agglevelsup);
COMPARE_LOCATION_FIELD(location);
***************
*** 2015,2020 **** _equalFuncCall(const FuncCall *a, const FuncCall *b)
--- 2018,2024 ----
COMPARE_SCALAR_FIELD(agg_star);
COMPARE_SCALAR_FIELD(agg_distinct);
COMPARE_SCALAR_FIELD(func_variadic);
+ COMPARE_SCALAR_FIELD(has_within_group);
COMPARE_NODE_FIELD(over);
COMPARE_LOCATION_FIELD(location);
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
***************
*** 558,563 **** makeFuncCall(List *name, List *args, int location)
--- 558,564 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->has_within_group = FALSE;
n->over = NULL;
return n;
}
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 1631,1636 **** expression_tree_walker(Node *node,
--- 1631,1641 ----
if (expression_tree_walker((Node *) expr->aggdistinct,
walker, context))
return true;
+
+ if (expression_tree_walker((Node *) expr->orddirectargs,
+ walker, context))
+ return true;
+
if (walker((Node *) expr->aggfilter, context))
return true;
}
***************
*** 2155,2161 **** expression_tree_mutator(Node *node,
--- 2160,2168 ----
MUTATE(newnode->args, aggref->args, List *);
MUTATE(newnode->aggorder, aggref->aggorder, List *);
MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
+ MUTATE(newnode->orddirectargs, aggref->orddirectargs, List *);
MUTATE(newnode->aggfilter, aggref->aggfilter, Expr *);
+
return (Node *) newnode;
}
break;
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 960,968 **** _outAggref(StringInfo str, const Aggref *node)
--- 960,971 ----
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(aggorder);
WRITE_NODE_FIELD(aggdistinct);
+ WRITE_NODE_FIELD(orddirectargs);
WRITE_NODE_FIELD(aggfilter);
WRITE_BOOL_FIELD(aggstar);
WRITE_BOOL_FIELD(aggvariadic);
+ WRITE_BOOL_FIELD(isordset);
+ WRITE_BOOL_FIELD(ishypothetical);
WRITE_UINT_FIELD(agglevelsup);
WRITE_LOCATION_FIELD(location);
}
***************
*** 2091,2096 **** _outFuncCall(StringInfo str, const FuncCall *node)
--- 2094,2100 ----
WRITE_BOOL_FIELD(agg_star);
WRITE_BOOL_FIELD(agg_distinct);
WRITE_BOOL_FIELD(func_variadic);
+ WRITE_BOOL_FIELD(has_within_group);
WRITE_NODE_FIELD(over);
WRITE_LOCATION_FIELD(location);
}
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 495,503 **** _readAggref(void)
--- 495,506 ----
READ_NODE_FIELD(args);
READ_NODE_FIELD(aggorder);
READ_NODE_FIELD(aggdistinct);
+ READ_NODE_FIELD(orddirectargs);
READ_NODE_FIELD(aggfilter);
READ_BOOL_FIELD(aggstar);
READ_BOOL_FIELD(aggvariadic);
+ READ_BOOL_FIELD(isordset);
+ READ_BOOL_FIELD(ishypothetical);
READ_UINT_FIELD(agglevelsup);
READ_LOCATION_FIELD(location);
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
***************
*** 39,44 ****
--- 39,45 ----
#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
+ #include "parser/parse_agg.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
***************
*** 465,471 **** count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
QualCost argcosts;
Oid *inputTypes;
int numArguments;
- ListCell *l;
Assert(aggref->agglevelsup == 0);
--- 466,471 ----
***************
*** 488,494 **** count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
costs->numOrderedAggs++;
/* add component function execution costs to appropriate totals */
! costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost;
if (OidIsValid(aggfinalfn))
costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost;
--- 488,495 ----
costs->numOrderedAggs++;
/* add component function execution costs to appropriate totals */
! if (OidIsValid(aggtransfn))
! costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost;
if (OidIsValid(aggfinalfn))
costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost;
***************
*** 506,589 **** count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
costs->transCost.startup += argcosts.startup;
costs->transCost.per_tuple += argcosts.per_tuple;
- /* extract argument types (ignoring any ORDER BY expressions) */
- inputTypes = (Oid *) palloc(sizeof(Oid) * list_length(aggref->args));
- numArguments = 0;
- foreach(l, aggref->args)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (!tle->resjunk)
- inputTypes[numArguments++] = exprType((Node *) tle->expr);
- }
-
- /* resolve actual type of transition state, if polymorphic */
- if (IsPolymorphicType(aggtranstype))
- {
- /* have to fetch the agg's declared input types... */
- Oid *declaredArgTypes;
- int agg_nargs;
-
- (void) get_func_signature(aggref->aggfnoid,
- &declaredArgTypes, &agg_nargs);
- Assert(agg_nargs == numArguments);
- aggtranstype = enforce_generic_type_consistency(inputTypes,
- declaredArgTypes,
- agg_nargs,
- aggtranstype,
- false);
- pfree(declaredArgTypes);
- }
-
/*
! * If the transition type is pass-by-value then it doesn't add
! * anything to the required size of the hashtable. If it is
! * pass-by-reference then we have to add the estimated size of the
! * value itself, plus palloc overhead.
*/
! if (!get_typbyval(aggtranstype))
{
! int32 avgwidth;
! /* Use average width if aggregate definition gave one */
! if (aggtransspace > 0)
! avgwidth = aggtransspace;
! else
{
/*
! * If transition state is of same type as first input, assume
! * it's the same typmod (same width) as well. This works for
! * cases like MAX/MIN and is probably somewhat reasonable
! * otherwise.
*/
! int32 aggtranstypmod;
! if (numArguments > 0 && aggtranstype == inputTypes[0])
! aggtranstypmod = exprTypmod((Node *) linitial(aggref->args));
else
! aggtranstypmod = -1;
! avgwidth = get_typavgwidth(aggtranstype, aggtranstypmod);
}
! avgwidth = MAXALIGN(avgwidth);
! costs->transitionSpace += avgwidth + 2 * sizeof(void *);
}
! else if (aggtranstype == INTERNALOID)
{
! /*
! * INTERNAL transition type is a special case: although INTERNAL
! * is pass-by-value, it's almost certainly being used as a pointer
! * to some large data structure. The aggregate definition can
! * provide an estimate of the size. If it doesn't, then we assume
! * ALLOCSET_DEFAULT_INITSIZE, which is a good guess if the data is
! * being kept in a private memory context, as is done by
! * array_agg() for instance.
! */
! if (aggtransspace > 0)
! costs->transitionSpace += aggtransspace;
! else
! costs->transitionSpace += ALLOCSET_DEFAULT_INITSIZE;
}
/*
--- 507,610 ----
costs->transCost.startup += argcosts.startup;
costs->transCost.per_tuple += argcosts.per_tuple;
/*
! * If we're doing a sorted agg, we can punt the entire
! * determination of transition element size since we're not
! * going to be using it to determine hashtable limits. This
! * simplifies the code for hypothetical set functions.
*/
!
! if (aggref->aggorder == NIL && aggref->aggdistinct == NIL)
{
! Assert(!aggref->isordset);
! /* extract argument types (ignoring any ORDER BY expressions) */
! inputTypes = (Oid *) palloc(sizeof(Oid) * FUNC_MAX_ARGS);
!
! numArguments = get_aggregate_argtypes(aggref, inputTypes, NULL);
!
! /* resolve actual type of transition state, if polymorphic */
! if (OidIsValid(aggtranstype) && IsPolymorphicType(aggtranstype))
{
+ /* have to fetch the agg's declared input types... */
+ Oid *declaredArgTypes;
+ int agg_nargs;
+
+ (void) get_func_signature(aggref->aggfnoid,
+ &declaredArgTypes, &agg_nargs);
+
/*
! * if variadic "any", might be more actual args than declared
! * args, but these extra args can't influence the determination
! * of polymorphic transition or result type.
*/
! Assert(agg_nargs <= numArguments);
!
! aggtranstype = enforce_generic_type_consistency(inputTypes,
! declaredArgTypes,
! agg_nargs,
! aggtranstype,
! false);
! pfree(declaredArgTypes);
! }
!
! /*
! * If the transition type is pass-by-value then it doesn't add
! * anything to the required size of the hashtable. If it is
! * pass-by-reference then we have to add the estimated size of the
! * value itself, plus palloc overhead.
! */
! if (OidIsValid(aggtranstype) && !get_typbyval(aggtranstype))
! {
! int32 avgwidth;
! /* Use average width if aggregate definition gave one */
! if (aggtransspace > 0)
! avgwidth = aggtransspace;
else
! {
! /*
! * If transition state is of same type as first input, assume
! * it's the same typmod (same width) as well. This works for
! * cases like MAX/MIN and is probably somewhat reasonable
! * otherwise.
! */
! int32 aggtranstypmod;
!
! if (numArguments > 0 && aggtranstype == inputTypes[0])
! aggtranstypmod = exprTypmod((Node *) linitial(aggref->args));
! else
! aggtranstypmod = -1;
! avgwidth = get_typavgwidth(aggtranstype, aggtranstypmod);
! }
!
! avgwidth = MAXALIGN(avgwidth);
!
! costs->transitionSpace += avgwidth + 2 * sizeof(void *);
! }
! else if (aggtranstype == INTERNALOID)
! {
! /*
! * INTERNAL transition type is a special case: although
! * INTERNAL is pass-by-value, it's almost certainly being used
! * as a pointer to some large data structure. The aggregate
! * definition can provide an estimate of the size. If it
! * doesn't, then we assume ALLOCSET_DEFAULT_INITSIZE, which is
! * a good guess if the data is being kept in a private memory
! * context, as is done by array_agg() for instance.
! */
! if (aggtransspace > 0)
! costs->transitionSpace += aggtransspace;
! else
! costs->transitionSpace += ALLOCSET_DEFAULT_INITSIZE;
}
! pfree(inputTypes);
}
! else
{
! costs->transitionSpace = work_mem; /* just in case */
}
/*
***************
*** 3911,3917 **** recheck_cast_function_args(List *args, Oid result_type, HeapTuple func_tuple)
elog(ERROR, "function's resolved result type changed during planning");
/* perform any necessary typecasting of arguments */
! make_fn_arguments(NULL, args, actual_arg_types, declared_arg_types);
}
/*
--- 3932,3938 ----
elog(ERROR, "function's resolved result type changed during planning");
/* perform any necessary typecasting of arguments */
! make_fn_arguments(NULL, args, NULL, actual_arg_types, declared_arg_types, false);
}
/*
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 951,957 **** transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
&qry->targetList,
EXPR_KIND_ORDER_BY,
true /* fix unknowns */ ,
! false /* allow SQL92 rules */ );
qry->groupClause = transformGroupClause(pstate,
stmt->groupClause,
--- 951,958 ----
&qry->targetList,
EXPR_KIND_ORDER_BY,
true /* fix unknowns */ ,
! false /* allow SQL92 rules */,
! false /* don't add duplicates */);
qry->groupClause = transformGroupClause(pstate,
stmt->groupClause,
***************
*** 1211,1217 **** transformValuesClause(ParseState *pstate, SelectStmt *stmt)
&qry->targetList,
EXPR_KIND_ORDER_BY,
true /* fix unknowns */ ,
! false /* allow SQL92 rules */ );
qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
EXPR_KIND_OFFSET, "OFFSET");
--- 1212,1219 ----
&qry->targetList,
EXPR_KIND_ORDER_BY,
true /* fix unknowns */ ,
! false /* allow SQL92 rules */,
! false /* don't add duplicates */ );
qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
EXPR_KIND_OFFSET, "OFFSET");
***************
*** 1435,1441 **** transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
&qry->targetList,
EXPR_KIND_ORDER_BY,
false /* no unknowns expected */ ,
! false /* allow SQL92 rules */ );
/* restore namespace, remove jrte from rtable */
pstate->p_namespace = sv_namespace;
--- 1437,1444 ----
&qry->targetList,
EXPR_KIND_ORDER_BY,
false /* no unknowns expected */ ,
! false /* allow SQL92 rules */ ,
! false /* don't add duplicates */ );
/* restore namespace, remove jrte from rtable */
pstate->p_namespace = sv_namespace;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 495,500 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
--- 495,501 ----
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
%type <node> filter_clause
+ %type <list> within_group_clause
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
***************
*** 597,603 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
VERBOSE VERSION_P VIEW VOLATILE
! WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
XMLPI XMLROOT XMLSERIALIZE
--- 598,604 ----
VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
VERBOSE VERSION_P VIEW VOLATILE
! WHEN WHERE WITHIN WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
XMLPI XMLROOT XMLSERIALIZE
***************
*** 3704,3710 **** AlterExtensionContentsStmt:
n->action = $4;
n->objtype = OBJECT_AGGREGATE;
n->objname = $6;
! n->objargs = extractArgTypes($7);
$$ = (Node *)n;
}
| ALTER EXTENSION name add_drop CAST '(' Typename AS Typename ')'
--- 3705,3711 ----
n->action = $4;
n->objtype = OBJECT_AGGREGATE;
n->objname = $6;
! n->objargs = extractArgTypes(linitial($7));
$$ = (Node *)n;
}
| ALTER EXTENSION name add_drop CAST '(' Typename AS Typename ')'
***************
*** 5283,5289 **** CommentStmt:
CommentStmt *n = makeNode(CommentStmt);
n->objtype = OBJECT_AGGREGATE;
n->objname = $4;
! n->objargs = extractArgTypes($5);
n->comment = $7;
$$ = (Node *) n;
}
--- 5284,5290 ----
CommentStmt *n = makeNode(CommentStmt);
n->objtype = OBJECT_AGGREGATE;
n->objname = $4;
! n->objargs = extractArgTypes(linitial($5));
n->comment = $7;
$$ = (Node *) n;
}
***************
*** 5449,5455 **** SecLabelStmt:
n->provider = $3;
n->objtype = OBJECT_AGGREGATE;
n->objname = $6;
! n->objargs = extractArgTypes($7);
n->label = $9;
$$ = (Node *) n;
}
--- 5450,5456 ----
n->provider = $3;
n->objtype = OBJECT_AGGREGATE;
n->objname = $6;
! n->objargs = extractArgTypes(linitial($7));
n->label = $9;
$$ = (Node *) n;
}
***************
*** 6449,6457 **** aggr_arg: func_arg
}
;
! /* Zero-argument aggregates are named with * for consistency with COUNT(*) */
! aggr_args: '(' aggr_args_list ')' { $$ = $2; }
! | '(' '*' ')' { $$ = NIL; }
;
aggr_args_list:
--- 6450,6502 ----
}
;
! /*
! * Aggregate args (for create aggregate, etc.) are treated as follows:
! *
! * (*) - no args
! * (func_arg,func_arg,...) - normal agg with args
! * () within group (func_arg,...) - ordered set func with no direct args
! * (func_arg,...) within group (func_arg,...) - ordered set func with args
! * (func_arg,...) within group (*) - ordered set func variadic special case
! *
! * This doesn't correspond to anything in the spec because the spec doesn't
! * have any DDL to create or modify ordered set functions, so we're winging
! * it here.
! *
! * Almost everything we do with an ordered set function treats its arguments
! * as though they were a single list, with the direct and grouped arg types
! * concatenated. So for simplicity, we construct a single list here.
! *
! * But we still need to know when creating an agg (but not for referring to it
! * later) where the division between direct and ordered args is; so this
! * production returns a pair (arglist,num) where num is the number of direct
! * args, or -1 if no within group clause was used. Most users of aggr_args,
! * other than CREATE AGGREGATE, therefore only need to pay attention to
! * linitial($n).
! */
!
! aggr_args: '(' '*' ')'
! {
! $$ = list_make2(NIL, makeInteger(-1));
! }
! | '(' aggr_args_list ')'
! {
! $$ = list_make2($2, makeInteger(-1));
! }
! | '(' ')' WITHIN GROUP_P '(' aggr_args_list ')'
! {
! $$ = list_make2($6, makeInteger(0));
! }
! | '(' aggr_args_list ')' WITHIN GROUP_P '(' aggr_args_list ')'
! {
! int n = list_length($2);
! $$ = list_make2(list_concat($2,$7), makeInteger(n));
! }
! | '(' aggr_args_list ')' WITHIN GROUP_P '(' '*' ')'
! {
! int n = list_length($2);
! $$ = list_make2($2, makeInteger(n));
! }
;
aggr_args_list:
***************
*** 6657,6663 **** RemoveAggrStmt:
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_AGGREGATE;
n->objects = list_make1($3);
! n->arguments = list_make1(extractArgTypes($4));
n->behavior = $5;
n->missing_ok = false;
n->concurrent = false;
--- 6702,6708 ----
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_AGGREGATE;
n->objects = list_make1($3);
! n->arguments = list_make1(extractArgTypes(linitial($4)));
n->behavior = $5;
n->missing_ok = false;
n->concurrent = false;
***************
*** 6668,6674 **** RemoveAggrStmt:
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_AGGREGATE;
n->objects = list_make1($5);
! n->arguments = list_make1(extractArgTypes($6));
n->behavior = $7;
n->missing_ok = true;
n->concurrent = false;
--- 6713,6719 ----
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_AGGREGATE;
n->objects = list_make1($5);
! n->arguments = list_make1(extractArgTypes(linitial($6)));
n->behavior = $7;
n->missing_ok = true;
n->concurrent = false;
***************
*** 6884,6890 **** RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
RenameStmt *n = makeNode(RenameStmt);
n->renameType = OBJECT_AGGREGATE;
n->object = $3;
! n->objarg = extractArgTypes($4);
n->newname = $7;
n->missing_ok = false;
$$ = (Node *)n;
--- 6929,6935 ----
RenameStmt *n = makeNode(RenameStmt);
n->renameType = OBJECT_AGGREGATE;
n->object = $3;
! n->objarg = extractArgTypes(linitial($4));
n->newname = $7;
n->missing_ok = false;
$$ = (Node *)n;
***************
*** 7358,7364 **** AlterObjectSchemaStmt:
AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
n->objectType = OBJECT_AGGREGATE;
n->object = $3;
! n->objarg = extractArgTypes($4);
n->newschema = $7;
n->missing_ok = false;
$$ = (Node *)n;
--- 7403,7409 ----
AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
n->objectType = OBJECT_AGGREGATE;
n->object = $3;
! n->objarg = extractArgTypes(linitial($4));
n->newschema = $7;
n->missing_ok = false;
$$ = (Node *)n;
***************
*** 7587,7593 **** AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_AGGREGATE;
n->object = $3;
! n->objarg = extractArgTypes($4);
n->newowner = $7;
$$ = (Node *)n;
}
--- 7632,7638 ----
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_AGGREGATE;
n->object = $3;
! n->objarg = extractArgTypes(linitial($4));
n->newowner = $7;
$$ = (Node *)n;
}
***************
*** 9475,9480 **** sortby: a_expr USING qual_all_Op opt_nulls_order
--- 9520,9530 ----
;
+ within_group_clause:
+ WITHIN GROUP_P '(' sort_clause ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
select_limit:
limit_clause offset_clause { $$ = list_make2($2, $1); }
| offset_clause limit_clause { $$ = list_make2($1, $2); }
***************
*** 11180,11191 **** func_application: func_name '(' ')'
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
! func_expr: func_application filter_clause over_clause
{
! FuncCall *n = (FuncCall*)$1;
! n->agg_filter = $2;
! n->over = $3;
! $$ = (Node*)n;
}
| func_expr_common_subexpr
{ $$ = $1; }
--- 11230,11264 ----
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
! func_expr: func_application within_group_clause filter_clause over_clause
{
! FuncCall *n = (FuncCall *) $1;
! /*
! * the order clause for WITHIN GROUP and the one
! * for aggregate ORDER BY share a field, so we
! * have to check here that at most one is present.
! * We check for DISTINCT here to give a better
! * error position. Other consistency checks are
! * deferred to parse_func.c or parse_agg.c
! */
! if ($2 != NIL)
! {
! if (n->agg_order != NIL)
! ereport(ERROR,
! (errcode(ERRCODE_SYNTAX_ERROR),
! errmsg("cannot have multiple ORDER BY clauses with WITHIN GROUP"),
! parser_errposition(@2)));
! if (n->agg_distinct)
! ereport(ERROR,
! (errcode(ERRCODE_SYNTAX_ERROR),
! errmsg("cannot have DISTINCT and WITHIN GROUP together"),
! parser_errposition(@2)));
! n->agg_order = $2;
! n->has_within_group = TRUE;
! }
! n->agg_filter = $3;
! n->over = $4;
! $$ = (Node *) n;
}
| func_expr_common_subexpr
{ $$ = $1; }
***************
*** 12744,12749 **** unreserved_keyword:
--- 12817,12823 ----
| VIEW
| VOLATILE
| WHITESPACE_P
+ | WITHIN
| WITHOUT
| WORK
| WRAPPER
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
***************
*** 44,50 **** typedef struct
int sublevels_up;
} check_ungrouped_columns_context;
! static int check_agg_arguments(ParseState *pstate, List *args, Expr *filter);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
--- 44,52 ----
int sublevels_up;
} check_ungrouped_columns_context;
! static int check_agg_arguments(ParseState *pstate,
! List *args,
! List *agg_ordset, Expr *filter);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
***************
*** 75,81 **** static bool check_ungrouped_columns_walker(Node *node,
*/
void
transformAggregateCall(ParseState *pstate, Aggref *agg,
! List *args, List *aggorder, bool agg_distinct)
{
List *tlist;
List *torder;
--- 77,84 ----
*/
void
transformAggregateCall(ParseState *pstate, Aggref *agg,
! List *args, List *aggorder,
! bool agg_distinct, bool agg_within_group)
{
List *tlist;
List *torder;
***************
*** 93,104 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
*/
tlist = NIL;
attno = 1;
! foreach(lc, args)
{
! Expr *arg = (Expr *) lfirst(lc);
! TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false);
! tlist = lappend(tlist, tle);
}
/*
--- 96,119 ----
*/
tlist = NIL;
attno = 1;
!
! if (agg_within_group)
{
! agg->isordset = TRUE;
! agg->orddirectargs = args;
! }
! else
! {
! foreach(lc, args)
! {
! Expr *arg = (Expr *) lfirst(lc);
! TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false);
! tlist = lappend(tlist, tle);
! }
!
! agg->isordset = FALSE;
! agg->orddirectargs = NIL;
}
/*
***************
*** 109,114 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
--- 124,134 ----
*
* We need to mess with p_next_resno since it will be used to number any
* new targetlist entries.
+ *
+ * If and only if we're doing a WITHIN GROUP list, we preserve any
+ * duplicate expressions in the sort clause. This is needed because the
+ * sort clause of WITHIN GROUP is really an argument list, and we must
+ * keep the number and content of entries matching the specified input.
*/
save_next_resno = pstate->p_next_resno;
pstate->p_next_resno = attno;
***************
*** 118,124 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
&tlist,
EXPR_KIND_ORDER_BY,
true /* fix unknowns */ ,
! true /* force SQL99 rules */ );
/*
* If we have DISTINCT, transform that to produce a distinctList.
--- 138,145 ----
&tlist,
EXPR_KIND_ORDER_BY,
true /* fix unknowns */ ,
! true /* force SQL99 rules */ ,
! agg_within_group /* keep duplicates? */ );
/*
* If we have DISTINCT, transform that to produce a distinctList.
***************
*** 160,166 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
! min_varlevel = check_agg_arguments(pstate, agg->args, agg->aggfilter);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
--- 181,188 ----
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
! min_varlevel = check_agg_arguments(pstate,
! agg->args, agg->orddirectargs, agg->aggfilter);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
***************
*** 312,318 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
* which we can't know until we finish scanning the arguments.
*/
static int
! check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
{
int agglevel;
check_agg_arguments_context context;
--- 334,340 ----
* which we can't know until we finish scanning the arguments.
*/
static int
! check_agg_arguments(ParseState *pstate, List *args, List *agg_ordset, Expr *filter)
{
int agglevel;
check_agg_arguments_context context;
***************
*** 330,335 **** check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
--- 352,360 ----
check_agg_arguments_walker,
(void *) &context);
+ (void) expression_tree_walker((Node *) agg_ordset, check_agg_arguments_walker,
+ (void *) &context);
+
/*
* If we found no vars nor aggs at all, it's a level-zero aggregate;
* otherwise, its level is the minimum of vars or aggs.
***************
*** 353,360 **** check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("aggregate function calls cannot be nested"),
parser_errposition(pstate,
! locate_agg_of_level((Node *) args,
! agglevel))));
return agglevel;
}
--- 378,385 ----
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("aggregate function calls cannot be nested"),
parser_errposition(pstate,
! locate_agg_of_level((Node *) args,
! agglevel))));
return agglevel;
}
***************
*** 823,830 **** check_ungrouped_columns_walker(Node *node,
* We do need to look at aggregates of lower levels, however.
*/
if (IsA(node, Aggref) &&
! (int) ((Aggref *) node)->agglevelsup >= context->sublevels_up)
return false;
/*
* If we have any GROUP BY items that are not simple Vars, check to see if
--- 848,863 ----
* We do need to look at aggregates of lower levels, however.
*/
if (IsA(node, Aggref) &&
! (int) ((Aggref *) node)->agglevelsup > context->sublevels_up)
! {
return false;
+ }
+ else if (IsA(node, Aggref) &&
+ (int) ((Aggref *) node)->agglevelsup == context->sublevels_up)
+ {
+ return check_ungrouped_columns_walker((Node*)(((Aggref *)node)->orddirectargs),
+ context);
+ }
/*
* If we have any GROUP BY items that are not simple Vars, check to see if
***************
*** 1042,1044 **** build_aggregate_fnexprs(Oid *agg_input_types,
--- 1075,1172 ----
agg_input_collation,
COERCE_EXPLICIT_CALL);
}
+
+ void
+ build_orderedset_fnexprs(Oid *agg_input_types,
+ int agg_num_inputs,
+ bool agg_variadic,
+ Oid agg_result_type,
+ Oid agg_input_collation,
+ Oid *agg_input_collation_array,
+ Oid finalfn_oid,
+ Expr **finalfnexpr)
+ {
+ FuncExpr *fexpr;
+ Param *argp;
+ List *args = NIL;
+ int i = 0;
+
+ /*
+ * Build arg list to use in the finalfn FuncExpr node. We really only care
+ * that finalfn can discover the actual argument types at runtime using
+ * get_fn_expr_argtype(), so it's okay to use Param nodes that don't
+ * correspond to any real Param.
+ */
+ for (i = 0; i < agg_num_inputs; i++)
+ {
+ argp = makeNode(Param);
+ argp->paramkind = PARAM_EXEC;
+ argp->paramid = -1;
+ argp->paramtype = agg_input_types[i];
+ argp->paramtypmod = -1;
+ argp->paramcollid = agg_input_collation_array[i];
+ argp->location = -1;
+
+ args = lappend(args, argp);
+ }
+
+ fexpr = makeFuncExpr(finalfn_oid,
+ agg_result_type,
+ args,
+ InvalidOid,
+ agg_input_collation,
+ COERCE_EXPLICIT_CALL);
+ fexpr->funcvariadic = agg_variadic;
+ *finalfnexpr = (Expr *) fexpr;
+ }
+
+ int get_aggregate_argtypes(Aggref *aggref, Oid *inputTypes, Oid *inputCollations)
+ {
+ int numArguments = 0;
+ ListCell *lc;
+
+ if (!(aggref->isordset))
+ {
+ foreach(lc, aggref->args)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+ if (!tle->resjunk)
+ {
+ inputTypes[numArguments] = exprType((Node *) tle->expr);
+ if (inputCollations != NULL)
+ inputCollations[numArguments] = exprCollation((Node *) tle->expr);
+ ++numArguments;
+ }
+ }
+ }
+ else
+ {
+ foreach(lc, aggref->orddirectargs)
+ {
+ Node *expr_orddirectargs = lfirst(lc);
+
+ inputTypes[numArguments] = exprType(expr_orddirectargs);
+ if (inputCollations != NULL)
+ inputCollations[numArguments] = exprCollation(expr_orddirectargs);
+
+ ++numArguments;
+ }
+
+ if (!(aggref->ishypothetical))
+ {
+ foreach(lc, aggref->args)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+ inputTypes[numArguments] = exprType((Node *) tle->expr);
+ if (inputCollations != NULL)
+ inputCollations[numArguments] = exprCollation((Node *) tle->expr);
+
+ ++numArguments;
+ }
+ }
+ }
+
+ return numArguments;
+ }
*** a/src/backend/parser/parse_clause.c
--- b/src/backend/parser/parse_clause.c
***************
*** 71,77 **** static void checkExprIsVarFree(ParseState *pstate, Node *n,
static TargetEntry *findTargetlistEntrySQL92(ParseState *pstate, Node *node,
List **tlist, ParseExprKind exprKind);
static TargetEntry *findTargetlistEntrySQL99(ParseState *pstate, Node *node,
! List **tlist, ParseExprKind exprKind);
static int get_matching_location(int sortgroupref,
List *sortgrouprefs, List *exprs);
static List *addTargetToSortList(ParseState *pstate, TargetEntry *tle,
--- 71,78 ----
static TargetEntry *findTargetlistEntrySQL92(ParseState *pstate, Node *node,
List **tlist, ParseExprKind exprKind);
static TargetEntry *findTargetlistEntrySQL99(ParseState *pstate, Node *node,
! List **tlist, ParseExprKind exprKind,
! bool keepDuplicates);
static int get_matching_location(int sortgroupref,
List *sortgrouprefs, List *exprs);
static List *addTargetToSortList(ParseState *pstate, TargetEntry *tle,
***************
*** 1477,1483 **** findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
/*
* Otherwise, we have an expression, so process it per SQL99 rules.
*/
! return findTargetlistEntrySQL99(pstate, node, tlist, exprKind);
}
/*
--- 1478,1484 ----
/*
* Otherwise, we have an expression, so process it per SQL99 rules.
*/
! return findTargetlistEntrySQL99(pstate, node, tlist, exprKind, false);
}
/*
***************
*** 1492,1501 **** findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
* node the ORDER BY, GROUP BY, etc expression to be matched
* tlist the target list (passed by reference so we can append to it)
* exprKind identifies clause type being processed
*/
static TargetEntry *
findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
! ParseExprKind exprKind)
{
TargetEntry *target_result;
ListCell *tl;
--- 1493,1503 ----
* node the ORDER BY, GROUP BY, etc expression to be matched
* tlist the target list (passed by reference so we can append to it)
* exprKind identifies clause type being processed
+ * keepDuplicates if true, don't try and match to any existing entry
*/
static TargetEntry *
findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
! ParseExprKind exprKind, bool keepDuplicates)
{
TargetEntry *target_result;
ListCell *tl;
***************
*** 1510,1533 **** findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
*/
expr = transformExpr(pstate, node, exprKind);
! foreach(tl, *tlist)
{
! TargetEntry *tle = (TargetEntry *) lfirst(tl);
! Node *texpr;
! /*
! * Ignore any implicit cast on the existing tlist expression.
! *
! * This essentially allows the ORDER/GROUP/etc item to adopt the same
! * datatype previously selected for a textually-equivalent tlist item.
! * There can't be any implicit cast at top level in an ordinary SELECT
! * tlist at this stage, but the case does arise with ORDER BY in an
! * aggregate function.
! */
! texpr = strip_implicit_coercions((Node *) tle->expr);
! if (equal(expr, texpr))
! return tle;
}
/*
--- 1512,1538 ----
*/
expr = transformExpr(pstate, node, exprKind);
! if (!keepDuplicates)
{
! foreach(tl, *tlist)
! {
! TargetEntry *tle = (TargetEntry *) lfirst(tl);
! Node *texpr;
! /*
! * Ignore any implicit cast on the existing tlist expression.
! *
! * This essentially allows the ORDER/GROUP/etc item to adopt the same
! * datatype previously selected for a textually-equivalent tlist item.
! * There can't be any implicit cast at top level in an ordinary SELECT
! * tlist at this stage, but the case does arise with ORDER BY in an
! * aggregate function.
! */
! texpr = strip_implicit_coercions((Node *) tle->expr);
! if (equal(expr, texpr))
! return tle;
! }
}
/*
***************
*** 1569,1575 **** transformGroupClause(ParseState *pstate, List *grouplist,
if (useSQL99)
tle = findTargetlistEntrySQL99(pstate, gexpr,
! targetlist, exprKind);
else
tle = findTargetlistEntrySQL92(pstate, gexpr,
targetlist, exprKind);
--- 1574,1580 ----
if (useSQL99)
tle = findTargetlistEntrySQL99(pstate, gexpr,
! targetlist, exprKind, false);
else
tle = findTargetlistEntrySQL92(pstate, gexpr,
targetlist, exprKind);
***************
*** 1636,1646 **** transformSortClause(ParseState *pstate,
List **targetlist,
ParseExprKind exprKind,
bool resolveUnknown,
! bool useSQL99)
{
List *sortlist = NIL;
ListCell *olitem;
foreach(olitem, orderlist)
{
SortBy *sortby = (SortBy *) lfirst(olitem);
--- 1641,1654 ----
List **targetlist,
ParseExprKind exprKind,
bool resolveUnknown,
! bool useSQL99,
! bool keepDuplicates)
{
List *sortlist = NIL;
ListCell *olitem;
+ Assert(useSQL99 || !keepDuplicates);
+
foreach(olitem, orderlist)
{
SortBy *sortby = (SortBy *) lfirst(olitem);
***************
*** 1648,1654 **** transformSortClause(ParseState *pstate,
if (useSQL99)
tle = findTargetlistEntrySQL99(pstate, sortby->node,
! targetlist, exprKind);
else
tle = findTargetlistEntrySQL92(pstate, sortby->node,
targetlist, exprKind);
--- 1656,1662 ----
if (useSQL99)
tle = findTargetlistEntrySQL99(pstate, sortby->node,
! targetlist, exprKind, keepDuplicates);
else
tle = findTargetlistEntrySQL92(pstate, sortby->node,
targetlist, exprKind);
***************
*** 1718,1724 **** transformWindowDefinitions(ParseState *pstate,
targetlist,
EXPR_KIND_WINDOW_ORDER,
true /* fix unknowns */ ,
! true /* force SQL99 rules */ );
partitionClause = transformGroupClause(pstate,
windef->partitionClause,
targetlist,
--- 1726,1733 ----
targetlist,
EXPR_KIND_WINDOW_ORDER,
true /* fix unknowns */ ,
! true /* force SQL99 rules */,
! false /* don't add duplicates */);
partitionClause = transformGroupClause(pstate,
windef->partitionClause,
targetlist,
*** a/src/backend/parser/parse_collate.c
--- b/src/backend/parser/parse_collate.c
***************
*** 73,79 **** typedef struct
static bool assign_query_collations_walker(Node *node, ParseState *pstate);
static bool assign_collations_walker(Node *node,
assign_collations_context *context);
!
/*
* assign_query_collations()
--- 73,81 ----
static bool assign_query_collations_walker(Node *node, ParseState *pstate);
static bool assign_collations_walker(Node *node,
assign_collations_context *context);
! static void assign_aggregate_collations(Aggref *aggref,
! assign_collations_context *context,
! assign_collations_context *loccontext);
/*
* assign_query_collations()
***************
*** 564,607 **** assign_collations_walker(Node *node, assign_collations_context *context)
case T_Aggref:
{
/*
! * Aggref is a special case because expressions
! * used only for ordering shouldn't be taken to
! * conflict with each other or with regular args.
! * So we apply assign_expr_collations() to them
! * rather than passing down our loccontext.
! *
! * Note that we recurse to each TargetEntry, not
! * directly to its contained expression, so that
! * the case above for T_TargetEntry will apply
! * appropriate checks to agg ORDER BY items.
! *
! * Likewise, we assign collations for the (bool)
! * expression in aggfilter, independently of any
! * other args.
! *
! * We need not recurse into the aggorder or
! * aggdistinct lists, because those contain only
! * SortGroupClause nodes which we need not
! * process.
*/
Aggref *aggref = (Aggref *) node;
- ListCell *lc;
! foreach(lc, aggref->args)
! {
! TargetEntry *tle = (TargetEntry *) lfirst(lc);
!
! Assert(IsA(tle, TargetEntry));
! if (tle->resjunk)
! assign_expr_collations(context->pstate,
! (Node *) tle);
! else
! (void) assign_collations_walker((Node *) tle,
! &loccontext);
! }
assign_expr_collations(context->pstate,
! (Node *) aggref->aggfilter);
}
break;
case T_WindowFunc:
--- 566,581 ----
case T_Aggref:
{
/*
! * Aggref is special enough that we give it its own
! * function. The FILTER clause is independent of the
! * rest of the aggregate, however.
*/
Aggref *aggref = (Aggref *) node;
! assign_aggregate_collations(aggref, context, &loccontext);
assign_expr_collations(context->pstate,
! (Node *) aggref->aggfilter);
}
break;
case T_WindowFunc:
***************
*** 802,804 **** assign_collations_walker(Node *node, assign_collations_context *context)
--- 776,934 ----
return false;
}
+
+
+ /*
+ * Aggref is a special case because expressions used only for ordering
+ * shouldn't be taken to conflict with each other or with regular args. So we
+ * apply assign_expr_collations() to them rather than passing down our
+ * loccontext.
+ *
+ * Note that we recurse to each TargetEntry, not directly to its contained
+ * expression, so that the case above for T_TargetEntry will apply appropriate
+ * checks to agg ORDER BY items.
+ *
+ * We need not recurse into the aggorder or aggdistinct lists, because those
+ * contain only SortGroupClause nodes which we need not process.
+ *
+ * For ordered set functions, it's unfortunately unclear how best to proceed.
+ * The spec-defined inverse distribution functions have only one sort column
+ * and don't allow collatable types, but this is clearly unsatisfactory in the
+ * general case. Compromise by taking the sort column as part of the collation
+ * determination if, and only if, there is only one such column, and force the
+ * final choice of input collation down into the sort column if need be; but
+ * don't error out unless actually necessary (leaving it up to the function to
+ * handle the issue at runtime). This ugly wart is justified by the fact that
+ * there seems to be no other good way to get a result collation for
+ * percentile_* applied to a collatable type.
+ *
+ * But hypothetical set functions are special; they must have
+ * pairwise-assigned collations for each matching pair of args, and again we
+ * need to force the final choice of collation down into the sort column to
+ * ensure that the sort happens on the chosen collation. If there are any
+ * additional args (not allowed in the spec, but a user-defined function might
+ * have some), those contribute to the result collation in the normal way.
+ * (The hypothetical paired args never contribute to the result collation at
+ * all.)
+ */
+
+ static Expr *
+ relabel_expr_collation(Expr *expr, Oid newcollation)
+ {
+ RelabelType *node = makeNode(RelabelType);
+ node->arg = expr;
+ node->resulttype = exprType((Node *)expr);
+ node->resulttypmod = exprTypmod((Node *)expr);
+ node->resultcollid = newcollation;
+ node->relabelformat = COERCE_IMPLICIT_CAST;
+ node->location = exprLocation((Node *)expr);
+ return (Expr *) node;
+ }
+
+ static void
+ assign_aggregate_collations(Aggref *aggref,
+ assign_collations_context *context,
+ assign_collations_context *loccontext)
+ {
+ ListCell *lc;
+
+ if (aggref->ishypothetical)
+ {
+ /*-
+ * Hypothetical set function, i.e.
+ * func(..., a,b,c,...) within group (p,q,r,...)
+ *
+ * Any initial set of direct args (before "a") contributes to the
+ * result collation in the usual way for function args. But none of
+ * a,b,c... or p,q,r... contribute at all; instead, they must be
+ * paired up (as though UNIONed) and the sorted col's collation forced
+ * to the chosen value (so that we sort it correctly).
+ */
+ int initial_args = list_length(aggref->orddirectargs) - list_length(aggref->args);
+ ListCell *h_arg = list_head(aggref->orddirectargs);
+ ListCell *s_arg = list_head(aggref->args);
+
+ Assert(initial_args >= 0);
+
+ while (initial_args-- > 0)
+ {
+ (void) assign_collations_walker((Node *) lfirst(h_arg), loccontext);
+ h_arg = lnext(h_arg);
+ }
+
+ for_each_cell(h_arg,h_arg)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(s_arg);
+ Oid coll = select_common_collation(context->pstate,
+ list_make2(lfirst(h_arg),lfirst(s_arg)),
+ false);
+
+ /*
+ * we can only get InvalidOid here if the type is not collatable,
+ * so no need to try and relabel in that case.
+ */
+
+ if (OidIsValid(coll)
+ && coll != exprCollation((Node *)(tle->expr)))
+ {
+ tle->expr = relabel_expr_collation(tle->expr, coll);
+ }
+
+ s_arg = lnext(s_arg);
+ }
+ }
+ else if (aggref->isordset && list_length(aggref->args) == 1)
+ {
+ /*
+ * Ordered set func with one sorted arg
+ */
+ TargetEntry *tle = (TargetEntry *) linitial(aggref->args);
+
+ /* do the TLE first so that it won't error out on conflicts */
+
+ (void) assign_collations_walker((Node *) tle,
+ loccontext);
+
+ (void) assign_collations_walker((Node *) aggref->orddirectargs,
+ loccontext);
+
+ /*
+ * If the sort col is a collatable type, and we chose a collation,
+ * and it's not the one the sort col already has, then force the
+ * sort col's collation (which can't have been explicit) to the
+ * chosen one. Otherwise leave it alone.
+ */
+ if (type_is_collatable(exprType((Node *)(tle->expr)))
+ && (loccontext->strength == COLLATE_IMPLICIT
+ || loccontext->strength == COLLATE_EXPLICIT)
+ && exprCollation((Node *)(tle->expr)) != loccontext->collation)
+ {
+ tle->expr = relabel_expr_collation(tle->expr, loccontext->collation);
+ }
+ }
+ else
+ {
+ /*
+ * For this case, we do the direct args (if any) together, as is
+ * normal for functions, but args which are either used only for
+ * sorting or are only part of a WITHIN GROUP are processed
+ * individually.
+ */
+
+ (void) assign_collations_walker((Node *) aggref->orddirectargs,
+ loccontext);
+
+ foreach(lc, aggref->args)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+ Assert(IsA(tle, TargetEntry));
+ if (tle->resjunk)
+ assign_expr_collations(context->pstate,
+ (Node *) tle);
+ else
+ (void) assign_collations_walker((Node *) tle,
+ loccontext);
+ }
+ }
+ }
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
***************
*** 463,470 **** transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
newresult = ParseFuncOrColumn(pstate,
list_make1(n),
list_make1(result),
! NIL, NULL, false, false, false,
! NULL, true, location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
--- 463,470 ----
newresult = ParseFuncOrColumn(pstate,
list_make1(n),
list_make1(result),
! location,
! NULL);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
***************
*** 631,638 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
node = ParseFuncOrColumn(pstate,
list_make1(makeString(colname)),
list_make1(node),
! NIL, NULL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 631,637 ----
node = ParseFuncOrColumn(pstate,
list_make1(makeString(colname)),
list_make1(node),
! cref->location, NULL);
}
break;
}
***************
*** 676,683 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
node = ParseFuncOrColumn(pstate,
list_make1(makeString(colname)),
list_make1(node),
! NIL, NULL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 675,681 ----
node = ParseFuncOrColumn(pstate,
list_make1(makeString(colname)),
list_make1(node),
! cref->location, NULL);
}
break;
}
***************
*** 734,741 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
node = ParseFuncOrColumn(pstate,
list_make1(makeString(colname)),
list_make1(node),
! NIL, NULL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 732,738 ----
node = ParseFuncOrColumn(pstate,
list_make1(makeString(colname)),
list_make1(node),
! cref->location, NULL);
}
break;
}
***************
*** 1242,1279 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
{
List *targs;
ListCell *args;
- Expr *tagg_filter;
/* Transform the list of arguments ... */
targs = NIL;
foreach(args, fn->args)
{
! targs = lappend(targs, transformExprRecurse(pstate,
! (Node *) lfirst(args)));
}
- /*
- * Transform the aggregate filter using transformWhereClause(), to which
- * FILTER is virtually identical...
- */
- tagg_filter = NULL;
- if (fn->agg_filter != NULL)
- tagg_filter = (Expr *)
- transformWhereClause(pstate, (Node *) fn->agg_filter,
- EXPR_KIND_FILTER, "FILTER");
-
/* ... and hand off to ParseFuncOrColumn */
return ParseFuncOrColumn(pstate,
fn->funcname,
targs,
! fn->agg_order,
! tagg_filter,
! fn->agg_star,
! fn->agg_distinct,
! fn->func_variadic,
! fn->over,
! false,
! fn->location);
}
static Node *
--- 1239,1258 ----
{
List *targs;
ListCell *args;
/* Transform the list of arguments ... */
targs = NIL;
foreach(args, fn->args)
{
! targs = lappend(targs, transformExprRecurse(pstate, (Node *) lfirst(args)));
}
/* ... and hand off to ParseFuncOrColumn */
return ParseFuncOrColumn(pstate,
fn->funcname,
targs,
! fn->location,
! fn);
}
static Node *
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
***************
*** 17,32 ****
--- 17,35 ----
#include "access/htup_details.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+ #include "catalog/pg_aggregate.h"
#include "funcapi.h"
#include "lib/stringinfo.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_agg.h"
+ #include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
#include "parser/parse_type.h"
+ #include "parser/parse_expr.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
***************
*** 56,70 **** static Node *ParseComplexProjection(ParseState *pstate, char *funcname,
* Also, when is_column is true, we return NULL on failure rather than
* reporting a no-such-function error.
*
! * The argument expressions (in fargs) and filter must have been transformed
! * already. But the agg_order expressions, if any, have not been.
*/
Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
! List *agg_order, Expr *agg_filter,
! bool agg_star, bool agg_distinct, bool func_variadic,
! WindowDef *over, bool is_column, int location)
{
Oid rettype;
Oid funcid;
ListCell *l;
--- 59,79 ----
* Also, when is_column is true, we return NULL on failure rather than
* reporting a no-such-function error.
*
! * The argument expressions (in fargs) must have been transformed
! * already.
*/
Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
! int location, FuncCall *fn)
{
+ List *agg_order = (fn ? fn->agg_order : NIL);
+ Expr *agg_filter = NULL;
+ bool agg_star = (fn ? fn->agg_star : false);
+ bool agg_distinct = (fn ? fn->agg_distinct : false);
+ bool agg_within_group = (fn ? fn->has_within_group : false);
+ bool func_variadic = (fn ? fn->func_variadic : false);
+ WindowDef *over = (fn ? fn->over : NULL);
+ bool is_column = (fn == NULL);
Oid rettype;
Oid funcid;
ListCell *l;
***************
*** 81,86 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 90,101 ----
int nvargs;
Oid vatype;
FuncDetailCode fdresult;
+ int number_of_args = -1;
+ bool isordsetfunc = false;
+ bool ishypotheticalsetfunc = false;
+
+ /* Check if the function has WITHIN GROUP as well as distinct. */
+ Assert(!(agg_within_group && agg_distinct));
/*
* Most of the rest of the parser just assumes that functions do not have
***************
*** 98,103 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 113,127 ----
parser_errposition(pstate, location)));
/*
+ * Transform the aggregate filter using transformWhereClause(), to which
+ * FILTER is virtually identical...
+ */
+ if (fn && fn->agg_filter != NULL)
+ agg_filter = (Expr *)
+ transformWhereClause(pstate, (Node *) fn->agg_filter,
+ EXPR_KIND_FILTER, "FILTER");
+
+ /*
* Extract arg type info in preparation for function lookup.
*
* If any arguments are Param markers of type VOID, we discard them from
***************
*** 163,168 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 187,198 ----
}
}
+ if (agg_within_group && argnames)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ordered set functions cannot use named arguments"),
+ parser_errposition(pstate, location)));
+
if (fargs)
{
first_arg = linitial(fargs);
***************
*** 170,175 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 200,225 ----
}
/*
+ * If WITHIN GROUP is present, we need to call transformExpr on each
+ * SortBy node in agg_order, then call exprType and append to
+ * actual_arg_types, in order to get the types of values in WITHIN GROUP
+ * clause.
+ */
+ if (agg_within_group)
+ {
+ Assert(agg_order != NIL);
+
+ foreach(l, agg_order)
+ {
+ SortBy *arg = (SortBy *) lfirst(l);
+
+ arg->node = transformExpr(pstate, arg->node, EXPR_KIND_ORDER_BY);
+
+ actual_arg_types[nargs++] = exprType(arg->node);
+ }
+ }
+
+ /*
* Check for column projection: if function has one argument, and that
* argument is of complex type, and function name is not qualified, then
* the "function call" could be a projection. We also check that there
***************
*** 247,252 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 297,308 ----
errmsg("DISTINCT specified, but %s is not an aggregate function",
NameListToString(funcname)),
parser_errposition(pstate, location)));
+ if (agg_within_group)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("WITHIN GROUP specified, but %s is not an ordered set function",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
if (agg_order != NIL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 266,271 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 322,374 ----
NameListToString(funcname)),
parser_errposition(pstate, location)));
}
+ else if (fdresult == FUNCDETAIL_AGGREGATE)
+ {
+ HeapTuple tup;
+ Form_pg_aggregate classForm;
+
+ tup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for aggregate %u", funcid);
+
+ classForm = (Form_pg_aggregate) GETSTRUCT(tup);
+ isordsetfunc = classForm->aggisordsetfunc;
+
+ if (isordsetfunc)
+ {
+ if (classForm->aggordnargs == -2)
+ {
+ ishypotheticalsetfunc = true;
+
+ if (nvargs != 2*list_length(agg_order))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("function %s has %d ordering columns but %d hypothetical arguments",
+ NameListToString(funcname), list_length(agg_order), (nvargs - list_length(agg_order))),
+ parser_errposition(pstate, location)));
+ }
+ else
+ {
+ number_of_args = classForm->aggordnargs;
+ }
+
+ if (!agg_within_group)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("WITHIN GROUP is required for call to ordered set function %s",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
+
+ if (over)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("OVER clause not supported for call to ordered set function %s",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
+ }
+
+ ReleaseSysCache(tup);
+ }
else if (!(fdresult == FUNCDETAIL_AGGREGATE ||
fdresult == FUNCDETAIL_WINDOWFUNC))
{
***************
*** 351,363 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
false);
/* perform the necessary typecasting of arguments */
! make_fn_arguments(pstate, fargs, actual_arg_types, declared_arg_types);
/*
* If it's a variadic function call, transform the last nvargs arguments
* into an array --- unless it's an "any" variadic.
*/
! if (nvargs > 0 && declared_arg_types[nargs - 1] != ANYOID)
{
ArrayExpr *newa = makeNode(ArrayExpr);
int non_var_args = nargs - nvargs;
--- 454,469 ----
false);
/* perform the necessary typecasting of arguments */
! make_fn_arguments(pstate, fargs, (isordsetfunc) ? agg_order : NIL,
! actual_arg_types,
! declared_arg_types,
! ishypotheticalsetfunc);
/*
* If it's a variadic function call, transform the last nvargs arguments
* into an array --- unless it's an "any" variadic.
*/
! if (nvargs > 0 && vatype != ANYOID)
{
ArrayExpr *newa = makeNode(ArrayExpr);
int non_var_args = nargs - nvargs;
***************
*** 388,403 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
* When function is called with an explicit VARIADIC labeled parameter,
* and the declared_arg_type is "any", then sanity check the actual
* parameter type now - it must be an array.
*/
if (nargs > 0 && vatype == ANYOID && func_variadic)
{
! Oid va_arr_typid = actual_arg_types[nargs - 1];
if (!OidIsValid(get_element_type(va_arr_typid)))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("VARIADIC argument must be an array"),
! parser_errposition(pstate, exprLocation((Node *) llast(fargs)))));
}
/* build the appropriate output structure */
--- 494,524 ----
* When function is called with an explicit VARIADIC labeled parameter,
* and the declared_arg_type is "any", then sanity check the actual
* parameter type now - it must be an array.
+ *
+ * Also, it can't be a hypothetical set function, and if it's an ordered
+ * set function, the variadic labeled parameter is the last _direct_ arg,
+ * not an ordered arg. (In practice we're unlikely to get this far for
+ * hypotheticals, since make_fn_arguments would probably fail to unify
+ * types, but we can't change the order of these.)
*/
if (nargs > 0 && vatype == ANYOID && func_variadic)
{
! int ignore_args = (agg_within_group ? list_length(agg_order) : 0);
! Oid va_arr_typid = actual_arg_types[nargs - 1 - ignore_args];
!
! if (ishypotheticalsetfunc)
! ereport(ERROR,
! (errcode(ERRCODE_SYNTAX_ERROR),
! errmsg("explicit VARIADIC argument not allowed for hypothetical set function"),
! parser_errposition(pstate,
! exprLocation((Node *) list_nth(fargs, nargs - 1 - ignore_args)))));
if (!OidIsValid(get_element_type(va_arr_typid)))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("VARIADIC argument must be an array"),
! parser_errposition(pstate,
! exprLocation((Node *) list_nth(fargs, nargs - 1 - ignore_args)))));
}
/* build the appropriate output structure */
***************
*** 421,426 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 542,553 ----
/* aggregate function */
Aggref *aggref = makeNode(Aggref);
+ if (agg_within_group && !isordsetfunc)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("%s is not an ordered set function",
+ func_signature_string(funcname, nargs, NIL, actual_arg_types))));
+
aggref->aggfnoid = funcid;
aggref->aggtype = rettype;
/* aggcollid and inputcollid will be set by parse_collate.c */
***************
*** 428,441 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
aggref->aggfilter = agg_filter;
aggref->aggstar = agg_star;
aggref->aggvariadic = func_variadic;
/* agglevelsup will be set by transformAggregateCall */
aggref->location = location;
/*
* Reject attempt to call a parameterless aggregate without (*)
* syntax. This is mere pedantry but some folks insisted ...
*/
! if (fargs == NIL && !agg_star)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("%s(*) must be used to call a parameterless aggregate function",
--- 555,578 ----
aggref->aggfilter = agg_filter;
aggref->aggstar = agg_star;
aggref->aggvariadic = func_variadic;
+ aggref->ishypothetical = ishypotheticalsetfunc;
/* agglevelsup will be set by transformAggregateCall */
aggref->location = location;
+ if (isordsetfunc
+ && number_of_args >= 0
+ && number_of_args != list_length(fargs))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("incorrect number of direct arguments to ordered set function %s",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
+
/*
* Reject attempt to call a parameterless aggregate without (*)
* syntax. This is mere pedantry but some folks insisted ...
*/
! if (fargs == NIL && !agg_star && !agg_within_group)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("%s(*) must be used to call a parameterless aggregate function",
***************
*** 464,470 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
parser_errposition(pstate, location)));
/* parse_agg.c does additional aggregate-specific processing */
! transformAggregateCall(pstate, aggref, fargs, agg_order, agg_distinct);
retval = (Node *) aggref;
}
--- 601,608 ----
parser_errposition(pstate, location)));
/* parse_agg.c does additional aggregate-specific processing */
! transformAggregateCall(pstate, aggref, fargs, agg_order,
! agg_distinct, agg_within_group);
retval = (Node *) aggref;
}
***************
*** 473,478 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 611,622 ----
/* window function */
WindowFunc *wfunc = makeNode(WindowFunc);
+ if (agg_within_group)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("WITHIN GROUP not allowed in window functions"),
+ parser_errposition(pstate, location)));
+
/*
* True window functions must be called with a window definition.
*/
***************
*** 1363,1373 **** func_get_detail(List *funcname,
void
make_fn_arguments(ParseState *pstate,
List *fargs,
Oid *actual_arg_types,
! Oid *declared_arg_types)
{
ListCell *current_fargs;
int i = 0;
foreach(current_fargs, fargs)
{
--- 1507,1527 ----
void
make_fn_arguments(ParseState *pstate,
List *fargs,
+ List *agg_order,
Oid *actual_arg_types,
! Oid *declared_arg_types,
! bool requiresUnification)
{
ListCell *current_fargs;
+ ListCell *current_aoargs;
int i = 0;
+ int unify_offset = -1;
+
+ if (requiresUnification)
+ {
+ unify_offset = list_length(fargs) - list_length(agg_order);
+ Assert(unify_offset >= 0);
+ }
foreach(current_fargs, fargs)
{
***************
*** 1375,1380 **** make_fn_arguments(ParseState *pstate,
--- 1529,1535 ----
if (actual_arg_types[i] != declared_arg_types[i])
{
Node *node = (Node *) lfirst(current_fargs);
+ Node *temp = NULL;
/*
* If arg is a NamedArgExpr, coerce its input expr instead --- we
***************
*** 1395,1412 **** make_fn_arguments(ParseState *pstate,
}
else
{
! node = coerce_type(pstate,
! node,
! actual_arg_types[i],
! declared_arg_types[i], -1,
! COERCION_IMPLICIT,
! COERCE_IMPLICIT_CAST,
! -1);
! lfirst(current_fargs) = node;
}
}
i++;
}
}
/*
--- 1550,1615 ----
}
else
{
! /*
! * If we are dealing with a hypothetical set function, we
! * need to unify agg_order and fargs.
! */
!
! if (declared_arg_types[i] == ANYOID && requiresUnification)
! {
! Oid unification_oid;
! SortBy *unify_with = (SortBy *) list_nth(agg_order,i - unify_offset);
!
! unification_oid = select_common_type(pstate,
! list_make2(unify_with->node,node),
! "WITHIN GROUP",
! NULL);
!
! declared_arg_types[i + list_length(agg_order)] = unification_oid;
!
! temp = coerce_type(pstate,
! node,
! actual_arg_types[i],
! unification_oid, -1,
! COERCION_IMPLICIT,
! COERCE_IMPLICIT_CAST,
! -1);
! }
! else
! {
! temp = coerce_type(pstate,
! node,
! actual_arg_types[i],
! declared_arg_types[i], -1,
! COERCION_IMPLICIT,
! COERCE_IMPLICIT_CAST,
! -1);
! }
!
! lfirst(current_fargs) = temp;
}
}
i++;
}
+
+ foreach(current_aoargs, agg_order)
+ {
+ if (actual_arg_types[i] != declared_arg_types[i])
+ {
+ SortBy *node = (SortBy *) lfirst(current_aoargs);
+ Node *temp = NULL;
+
+ temp = coerce_type(pstate,
+ node->node,
+ actual_arg_types[i],
+ declared_arg_types[i], -1,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ node->node = temp;
+ }
+ i++;
+ }
}
/*
*** a/src/backend/parser/parse_oper.c
--- b/src/backend/parser/parse_oper.c
***************
*** 823,829 **** make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
false);
/* perform the necessary typecasting of arguments */
! make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
/* and build the expression node */
result = makeNode(OpExpr);
--- 823,829 ----
false);
/* perform the necessary typecasting of arguments */
! make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types, false);
/* and build the expression node */
result = makeNode(OpExpr);
***************
*** 953,959 **** make_scalar_array_op(ParseState *pstate, List *opname,
declared_arg_types[1] = res_atypeId;
/* perform the necessary typecasting of arguments */
! make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
/* and build the expression node */
result = makeNode(ScalarArrayOpExpr);
--- 953,959 ----
declared_arg_types[1] = res_atypeId;
/* perform the necessary typecasting of arguments */
! make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types, false);
/* and build the expression node */
result = makeNode(ScalarArrayOpExpr);
*** a/src/backend/utils/adt/Makefile
--- b/src/backend/utils/adt/Makefile
***************
*** 19,31 **** OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
array_userfuncs.o arrayutils.o bool.o \
cash.o char.o date.o datetime.o datum.o domains.o \
enum.o float.o format_type.o \
! geo_ops.o geo_selfuncs.o int.o int8.o json.o jsonfuncs.o like.o \
lockfuncs.o misc.o nabstime.o name.o numeric.o numutils.o \
oid.o oracle_compat.o pseudotypes.o rangetypes.o rangetypes_gist.o \
rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \
tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \
network.o mac.o inet_cidr_ntop.o inet_net_pton.o \
! ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \
ascii.o quote.o pgstatfuncs.o encode.o dbsize.o genfile.o trigfuncs.o \
tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
--- 19,31 ----
array_userfuncs.o arrayutils.o bool.o \
cash.o char.o date.o datetime.o datum.o domains.o \
enum.o float.o format_type.o \
! geo_ops.o geo_selfuncs.o hypotheticalset.o int.o int8.o json.o jsonfuncs.o like.o \
lockfuncs.o misc.o nabstime.o name.o numeric.o numutils.o \
oid.o oracle_compat.o pseudotypes.o rangetypes.o rangetypes_gist.o \
rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \
tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \
network.o mac.o inet_cidr_ntop.o inet_net_pton.o \
! inversedistribution.o ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \
ascii.o quote.o pgstatfuncs.o encode.o dbsize.o genfile.o trigfuncs.o \
tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
*** /dev/null
--- b/src/backend/utils/adt/hypotheticalset.c
***************
*** 0 ****
--- 1,223 ----
+ /*-------------------------------------------------------------------------
+ *
+ * hypotheticalset.c
+ * Hypothetical set functions.
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/hypotheticalset.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ #include "postgres.h"
+ #include "fmgr.h"
+ #include <string.h>
+ #include <math.h>
+
+ #include "utils/tuplesort.h"
+ #include "catalog/pg_type.h"
+ #include "utils/datetime.h"
+ #include "utils/builtins.h"
+ #include "executor/executor.h"
+
+ Datum hypothetical_rank_final(PG_FUNCTION_ARGS);
+ Datum hypothetical_dense_rank_final(PG_FUNCTION_ARGS);
+ Datum hypothetical_percent_rank_final(PG_FUNCTION_ARGS);
+ Datum hypothetical_cume_dist_final(PG_FUNCTION_ARGS);
+
+
+ /*
+ * Common code to sanity-check args for hypothetical set functions. No need
+ * for friendly errors, these can only happen if someone's messing up the
+ * aggregate definitions. The checks are needed for security, however; but we
+ * only need them once per call site. Store a pointer to the tupdesc as a
+ * sentinel.
+ */
+
+ static void
+ hypothetical_check_argtypes(FunctionCallInfo fcinfo, int nargs, TupleDesc tupdesc)
+ {
+ int i;
+
+ if (!tupdesc
+ || (nargs + 1) != tupdesc->natts
+ || tupdesc->attrs[nargs]->atttypid != BOOLOID)
+ elog(ERROR, "type mismatch in hypothetical set function");
+
+ for (i = 0; i < nargs; ++i)
+ if (get_fn_expr_argtype(fcinfo->flinfo,i) != tupdesc->attrs[i]->atttypid)
+ elog(ERROR, "type mismatch in hypothetical set function");
+
+ fcinfo->flinfo->fn_extra = tupdesc;
+ }
+
+ /*
+ * rank(float8) - rank of hypothetical row
+ */
+ Datum
+ hypothetical_rank_final(PG_FUNCTION_ARGS)
+ {
+ Tuplesortstate *sorter = NULL;
+ TupleDesc tupdesc = NULL;
+ TupleTableSlot *slot = NULL;
+ Oid datumtype = InvalidOid;
+ int nargs = PG_NARGS();
+ int i;
+ int64 rank = 1;
+
+ AggSetGetSortInfo(fcinfo, &sorter, &tupdesc, &slot, &datumtype);
+
+ if (fcinfo->flinfo->fn_extra == NULL
+ || fcinfo->flinfo->fn_extra != tupdesc)
+ hypothetical_check_argtypes(fcinfo, nargs, tupdesc);
+
+ /* insert the hypothetical row into the sort */
+
+ ExecClearTuple(slot);
+ for (i = 0; i < nargs; ++i)
+ {
+ slot->tts_values[i] = PG_GETARG_DATUM(i);
+ slot->tts_isnull[i] = PG_ARGISNULL(i);
+ }
+ slot->tts_values[nargs] = BoolGetDatum(true);
+ slot->tts_isnull[nargs] = false;
+ ExecStoreVirtualTuple(slot);
+
+ tuplesort_puttupleslot(sorter, slot);
+
+ tuplesort_performsort(sorter);
+
+ while (tuplesort_gettupleslot(sorter, true, slot))
+ {
+ bool isnull;
+ Datum d = slot_getattr(slot, nargs + 1, &isnull);
+
+ if (!isnull && DatumGetBool(d))
+ break;
+
+ ++rank;
+ }
+
+ ExecClearTuple(slot);
+
+ PG_RETURN_INT64(rank);
+ }
+
+ /*
+ * dense_rank(float8) - rank of hypothetical row
+ * without gap in ranking
+ */
+ Datum
+ hypothetical_dense_rank_final(PG_FUNCTION_ARGS)
+ {
+ Tuplesortstate *sorter = NULL;
+ TupleDesc tupdesc = NULL;
+ TupleTableSlot *slot = NULL;
+ Oid datumtype = InvalidOid;
+ int nargs = PG_NARGS();
+ int i;
+ int64 rank = 1;
+ int duplicate_count = 0;
+ TupleTableSlot *slot2 = NULL;
+ AttrNumber *colidx;
+ FmgrInfo *equalfns;
+ int numDistinctCol = 0;
+ MemoryContext memcontext;
+
+ AggSetGetSortInfo(fcinfo, &sorter, &tupdesc, &slot, &datumtype);
+
+ if (fcinfo->flinfo->fn_extra == NULL
+ || fcinfo->flinfo->fn_extra != tupdesc)
+ hypothetical_check_argtypes(fcinfo, nargs, tupdesc);
+
+ /* insert the hypothetical row into the sort */
+
+ ExecClearTuple(slot);
+ for (i = 0; i < nargs; ++i)
+ {
+ slot->tts_values[i] = PG_GETARG_DATUM(i);
+ slot->tts_isnull[i] = PG_ARGISNULL(i);
+ }
+ slot->tts_values[nargs] = BoolGetDatum(true);
+ slot->tts_isnull[nargs] = false;
+ ExecStoreVirtualTuple(slot);
+
+ tuplesort_puttupleslot(sorter, slot);
+
+ tuplesort_performsort(sorter);
+
+ numDistinctCol = AggSetGetDistinctInfo(fcinfo, &slot2, &colidx, &equalfns);
+
+ ExecClearTuple(slot2);
+
+ AggSetGetPerTupleContext(fcinfo, &memcontext);
+
+ while (tuplesort_gettupleslot(sorter, true, slot))
+ {
+ TupleTableSlot *tmpslot = slot2;
+ bool isnull;
+ Datum d = slot_getattr(slot, nargs + 1, &isnull);
+
+ if (!isnull && DatumGetBool(d))
+ break;
+
+ if (!TupIsNull(slot2)
+ && execTuplesMatch(slot, slot2,
+ (numDistinctCol - 1),
+ colidx,
+ equalfns,
+ memcontext))
+ ++duplicate_count;
+
+ slot2 = slot;
+ slot = tmpslot;
+
+ ++rank;
+ }
+
+ ExecClearTuple(slot);
+ ExecClearTuple(slot2);
+
+ rank = rank - duplicate_count;
+ PG_RETURN_INT64(rank);
+ }
+
+ /* percent_rank(float8)
+ * Calculates the relative ranking of hypothetical
+ * row within a group
+ */
+
+ Datum
+ hypothetical_percent_rank_final(PG_FUNCTION_ARGS)
+ {
+ Datum rank = hypothetical_rank_final(fcinfo);
+ int64 rank_val = DatumGetInt64(rank);
+ int64 rowcount = AggSetGetRowCount(fcinfo) + 1;
+ float8 result_val = 0.0;
+
+ if (rowcount == 1)
+ PG_RETURN_FLOAT8(0);
+
+ result_val = (float8) (rank_val - 1) / (float8) (rowcount - 1);
+
+ PG_RETURN_FLOAT8(result_val);
+ }
+
+ /* cume_dist - cumulative distribution of hypothetical
+ * row in a group
+ */
+
+ Datum
+ hypothetical_cume_dist_final(PG_FUNCTION_ARGS)
+ {
+ Datum rank = hypothetical_rank_final(fcinfo);
+ int64 rank_val = DatumGetInt64(rank);
+ int64 rowcount = AggSetGetRowCount(fcinfo) + 1;
+
+ float8 result_val = (float8) (rank_val) / (float8) (rowcount);
+
+ PG_RETURN_FLOAT8(result_val);
+ }
*** /dev/null
--- b/src/backend/utils/adt/inversedistribution.c
***************
*** 0 ****
--- 1,662 ----
+ /*-------------------------------------------------------------------------
+ *
+ * inversedistribution.c
+ * Inverse distribution functions.
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/inversedistribution.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ #include "postgres.h"
+ #include "fmgr.h"
+ #include <string.h>
+ #include <math.h>
+
+ #include "utils/tuplesort.h"
+ #include "catalog/pg_type.h"
+ #include "utils/datetime.h"
+ #include "utils/lsyscache.h"
+ #include "utils/array.h"
+
+ /*
+ * percentile_disc(float8) - discrete percentile
+ */
+
+ Datum percentile_disc_final(PG_FUNCTION_ARGS);
+
+ Datum
+ percentile_disc_final(PG_FUNCTION_ARGS)
+ {
+ float8 percentile;
+ Tuplesortstate *sorter;
+ Oid datumtype;
+ Datum val;
+ bool isnull;
+ int64 skiprows;
+ int64 rowcount = AggSetGetRowCount(fcinfo);
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+
+ percentile = PG_GETARG_FLOAT8(0);
+
+ AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+
+ if (percentile < 0 || percentile > 1 || isnan(percentile))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("percentile value %g must be between 0 and 1", percentile)));
+
+ if (rowcount < 1)
+ PG_RETURN_NULL();
+
+ tuplesort_performsort(sorter);
+
+ /*
+ * We need the smallest K such that (K/N) >= percentile. K starts at 1.
+ * Therefore K >= N*percentile
+ * Therefore K = ceil(N*percentile)
+ * So we skip K-1 rows (if K>0) and return the next row fetched.
+ *
+ * We don't actually expect to see nulls in the input, our strict flag
+ * should have filtered them out, but we're required to not crash if
+ * there is one.
+ */
+
+ skiprows = (int64) ceil(percentile * rowcount);
+ Assert(skiprows <= rowcount);
+
+ while (--skiprows > 0)
+ if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ elog(ERROR,"missing row in percentile_disc");
+
+ if (!tuplesort_getdatum(sorter, true, &val, &isnull))
+ elog(ERROR,"missing row in percentile_disc");
+
+ if (isnull)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_DATUM(val);
+ }
+
+
+ /*
+ * For percentile_cont, we need a way to interpolate between consecutive
+ * values. Use a helper function for that, so that we can share the rest
+ * of the code between types.
+ */
+
+ static Datum float8_lerp(Datum lo, Datum hi, float8 pct)
+ {
+ float8 loval = DatumGetFloat8(lo);
+ float8 hival = DatumGetFloat8(hi);
+ return Float8GetDatum(loval + (pct * (hival - loval)));
+ }
+
+ static Datum interval_lerp(Datum lo, Datum hi, float8 pct)
+ {
+ Datum diff_result = DirectFunctionCall2(interval_mi, hi, lo);
+ Datum mul_result = DirectFunctionCall2(interval_mul,
+ diff_result,
+ Float8GetDatumFast(pct));
+ return DirectFunctionCall2(interval_pl, mul_result, lo);
+ }
+
+ typedef Datum (*LerpFunc)(Datum lo, Datum hi, float8 pct);
+
+ static Datum
+ percentile_cont_final_common(FunctionCallInfo fcinfo,
+ Oid expect_type,
+ LerpFunc lerpfunc)
+ {
+ float8 percentile;
+ int64 rowcount = AggSetGetRowCount(fcinfo);
+ Tuplesortstate *sorter;
+ Oid datumtype;
+ Datum val;
+ Datum first_row;
+ Datum second_row;
+ float8 proportion;
+ bool isnull;
+ int64 skiprows;
+ int64 lower_row = 0;
+ int64 higher_row = 0;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+
+ percentile = PG_GETARG_FLOAT8(0);
+
+ AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+
+ Assert(datumtype == expect_type);
+
+ if (percentile < 0 || percentile > 1 || isnan(percentile))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("percentile value %g must be between 0 and 1", percentile)));
+
+ if (rowcount < 1)
+ PG_RETURN_NULL();
+
+ tuplesort_performsort(sorter);
+
+ lower_row = floor(percentile * (rowcount - 1));
+ higher_row = ceil(percentile * (rowcount - 1));
+
+ Assert(lower_row < rowcount);
+
+ for (skiprows = lower_row; skiprows > 0; --skiprows)
+ if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ elog(ERROR,"missing row in percentile_cont");
+
+ if (!tuplesort_getdatum(sorter, true, &first_row, &isnull))
+ elog(ERROR,"missing row in percentile_cont");
+ if (isnull)
+ PG_RETURN_NULL();
+
+ if (lower_row == higher_row)
+ {
+ val = first_row;
+ }
+ else
+ {
+ if (!tuplesort_getdatum(sorter, true, &second_row, &isnull))
+ elog(ERROR,"missing row in percentile_cont");
+
+ if (isnull)
+ PG_RETURN_NULL();
+
+ proportion = (percentile * (rowcount-1)) - lower_row;
+ val = lerpfunc(first_row, second_row, proportion);
+ }
+
+ if (isnull)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_DATUM(val);
+ }
+
+
+
+ /*
+ * percentile_cont(float8) - continuous percentile
+ */
+
+ Datum percentile_cont_float8_final(PG_FUNCTION_ARGS);
+ Datum percentile_cont_interval_final(PG_FUNCTION_ARGS);
+
+ Datum
+ percentile_cont_float8_final(PG_FUNCTION_ARGS)
+ {
+ return percentile_cont_final_common(fcinfo, FLOAT8OID, float8_lerp);
+ }
+
+ /*
+ * percentile_interval_cont(Interval) - continuous percentile for Interval
+ */
+
+ Datum
+ percentile_cont_interval_final(PG_FUNCTION_ARGS)
+ {
+ return percentile_cont_final_common(fcinfo, INTERVALOID, interval_lerp);
+ }
+
+
+ /*
+ * mode() - most common value
+ */
+
+ Datum mode_final(PG_FUNCTION_ARGS);
+
+ Datum
+ mode_final(PG_FUNCTION_ARGS)
+ {
+ Tuplesortstate *sorter;
+ Oid datumtype;
+ bool isnull;
+ Datum val;
+ Datum last_val = (Datum) 0;
+ bool last_val_is_mode = false;
+ int64 val_freq = 0;
+ Datum mode_val = (Datum) 0;
+ int64 mode_freq = 0;
+ FmgrInfo *equalfn;
+ bool shouldfree;
+
+ struct mode_type_info {
+ Oid typid;
+ int16 typLen;
+ bool typByVal;
+ } *typinfo = fcinfo->flinfo->fn_extra;
+
+ AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ AggSetGetDistinctInfo(fcinfo, NULL, NULL, &equalfn);
+
+ if (!typinfo || typinfo->typid != datumtype)
+ {
+ if (typinfo)
+ pfree(typinfo);
+ typinfo = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ sizeof(struct mode_type_info));
+ typinfo->typid = datumtype;
+ get_typlenbyval(datumtype, &typinfo->typLen, &typinfo->typByVal);
+ }
+
+ shouldfree = !(typinfo->typByVal);
+
+ tuplesort_performsort(sorter);
+
+ while (tuplesort_getdatum(sorter, true, &val, &isnull))
+ {
+ if (isnull)
+ continue;
+
+ if (val_freq == 0)
+ {
+ /* first value - assume modal until shown otherwise */
+ mode_val = last_val = val;
+ mode_freq = val_freq = 1;
+ last_val_is_mode = true;
+ }
+ else if (DatumGetBool(FunctionCall2(equalfn, val, last_val)))
+ {
+ /* value equal to previous value */
+ if (last_val_is_mode)
+ ++mode_freq;
+ else if (++val_freq > mode_freq)
+ {
+ if (shouldfree)
+ {
+ pfree(DatumGetPointer(mode_val));
+ pfree(DatumGetPointer(val));
+ }
+
+ mode_val = last_val;
+ mode_freq = val_freq;
+ last_val_is_mode = true;
+ }
+ else if (shouldfree)
+ pfree(DatumGetPointer(val));
+ }
+ else
+ {
+ if (shouldfree && !last_val_is_mode)
+ pfree(DatumGetPointer(last_val));
+
+ last_val_is_mode = false;
+ last_val = val;
+ val_freq = 1;
+ }
+ }
+
+ if (shouldfree && !last_val_is_mode)
+ pfree(DatumGetPointer(last_val));
+
+ if (mode_freq)
+ PG_RETURN_DATUM(mode_val);
+ else
+ PG_RETURN_NULL();
+ }
+
+
+
+ /*
+ * percentile_disc(float8[]) - discrete percentiles
+ */
+
+ Datum percentile_disc_multi_final(PG_FUNCTION_ARGS);
+
+ struct pct_info {
+ int64 first_row;
+ int64 second_row;
+ float8 proportion;
+ int idx;
+ };
+
+ static int pct_info_cmp(const void *pa, const void *pb)
+ {
+ const struct pct_info *a = pa;
+ const struct pct_info *b = pb;
+ if (a->first_row == b->first_row)
+ return (a->second_row < b->second_row) ? -1 : (a->second_row == b->second_row) ? 0 : 1;
+ else
+ return (a->first_row < b->first_row) ? -1 : 1;
+ }
+
+ static struct pct_info *setup_pct_info(int num_percentiles,
+ Datum *percentiles_datum,
+ bool *percentiles_null,
+ int64 rowcount,
+ bool continuous)
+ {
+ struct pct_info *pct_info = palloc(num_percentiles * sizeof(struct pct_info));
+ int i;
+
+ for (i = 0; i < num_percentiles; i++)
+ {
+ pct_info[i].idx = i;
+
+ if (percentiles_null[i])
+ {
+ pct_info[i].first_row = 0;
+ pct_info[i].second_row = 0;
+ pct_info[i].proportion = 0;
+ }
+ else
+ {
+ float8 p = DatumGetFloat8(percentiles_datum[i]);
+
+ if (p < 0 || p > 1 || isnan(p))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("percentile value %g must be between 0 and 1", p)));
+
+ if (continuous)
+ {
+ pct_info[i].first_row = 1 + floor(p * (rowcount - 1));
+ pct_info[i].second_row = 1 + ceil(p * (rowcount - 1));
+ pct_info[i].proportion = (p * (rowcount-1)) - floor(p * (rowcount-1));
+ }
+ else
+ {
+ /*
+ * We need the smallest K such that (K/N) >= percentile. K starts at 1.
+ * Therefore K >= N*percentile
+ * Therefore K = ceil(N*percentile), minimum 1
+ */
+
+ pct_info[i].first_row = Max(1, (int64) ceil(rowcount * p));
+ pct_info[i].second_row = 0;
+ pct_info[i].proportion = 0;
+ }
+ }
+ }
+
+ qsort(pct_info, num_percentiles, sizeof(struct pct_info), pct_info_cmp);
+
+ return pct_info;
+ }
+
+ Datum
+ percentile_disc_multi_final(PG_FUNCTION_ARGS)
+ {
+ ArrayType *param;
+ Datum *percentiles_datum;
+ bool *percentiles_null;
+ int num_percentiles;
+ int64 rowcount = AggSetGetRowCount(fcinfo);
+ int64 rownum = 0;
+ Tuplesortstate *sorter;
+ Oid datumtype;
+ Datum val;
+ bool isnull;
+ Datum *result_datum;
+ bool *result_isnull;
+ int i;
+ struct pct_info *pct_info;
+
+ struct mode_type_info {
+ Oid typid;
+ int16 typLen;
+ bool typByVal;
+ char typAlign;
+ } *typinfo = fcinfo->flinfo->fn_extra;
+
+ if (PG_ARGISNULL(0) || rowcount < 1)
+ PG_RETURN_NULL();
+
+ AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+
+ if (!typinfo || typinfo->typid != datumtype)
+ {
+ if (typinfo)
+ pfree(typinfo);
+ typinfo = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ sizeof(struct mode_type_info));
+ typinfo->typid = datumtype;
+ get_typlenbyvalalign(datumtype,
+ &typinfo->typLen,
+ &typinfo->typByVal,
+ &typinfo->typAlign);
+ }
+
+ param = PG_GETARG_ARRAYTYPE_P(0);
+
+ deconstruct_array(param, FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd',
+ &percentiles_datum, &percentiles_null, &num_percentiles);
+
+ if (num_percentiles == 0)
+ PG_RETURN_POINTER(construct_empty_array(datumtype));
+
+ result_datum = palloc0(num_percentiles * sizeof(Datum));
+ result_isnull = palloc0(num_percentiles * sizeof(bool));
+
+ pct_info = setup_pct_info(num_percentiles,
+ percentiles_datum,
+ percentiles_null,
+ rowcount,
+ false);
+
+ /*
+ * Start by dealing with any nulls in the param array - those are
+ * sorted to the front on row=0, so set the corresponding result
+ * indexes to null
+ */
+ for (i = 0; i < num_percentiles; ++i)
+ {
+ int idx = pct_info[i].idx;
+
+ if (pct_info[i].first_row > 0)
+ break;
+
+ result_datum[idx] = (Datum) 0;
+ result_isnull[idx] = true;
+ }
+
+ /*
+ * If there's anything left after doing the nulls, then grind the
+ * input and extract the needed values
+ */
+ if (i < num_percentiles)
+ {
+ tuplesort_performsort(sorter);
+
+ for (; i < num_percentiles; ++i)
+ {
+ int64 target_row = pct_info[i].first_row;
+ int idx = pct_info[i].idx;
+
+ if (target_row > rownum)
+ {
+ while (target_row > ++rownum)
+ {
+ if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ elog(ERROR,"missing row in percentile_disc");
+ }
+
+ if (!tuplesort_getdatum(sorter, true, &val, &isnull))
+ elog(ERROR,"missing row in percentile_disc");
+ }
+
+ result_datum[idx] = val;
+ result_isnull[idx] = isnull;
+ }
+ }
+
+ /* We make the output array the same shape as the input */
+
+ PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull,
+ ARR_NDIM(param),
+ ARR_DIMS(param), ARR_LBOUND(param),
+ datumtype,
+ typinfo->typLen,
+ typinfo->typByVal,
+ typinfo->typAlign));
+ }
+
+ static Datum
+ percentile_cont_multi_final_common(FunctionCallInfo fcinfo,
+ Oid expect_type,
+ int16 typLen, bool typByVal, char typAlign,
+ LerpFunc lerpfunc)
+ {
+ ArrayType *param;
+ Datum *percentiles_datum;
+ bool *percentiles_null;
+ int num_percentiles;
+ int64 rowcount = AggSetGetRowCount(fcinfo);
+ int64 rownum = 0;
+ int64 rownum_second = 0;
+ Tuplesortstate *sorter;
+ Oid datumtype;
+ Datum first_val;
+ Datum second_val;
+ bool isnull;
+ Datum *result_datum;
+ bool *result_isnull;
+ int i;
+ struct pct_info *pct_info;
+
+ if (PG_ARGISNULL(0) || rowcount < 1)
+ PG_RETURN_NULL();
+
+ AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype);
+ Assert(datumtype == expect_type);
+
+ param = PG_GETARG_ARRAYTYPE_P(0);
+
+ deconstruct_array(param, FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd',
+ &percentiles_datum, &percentiles_null, &num_percentiles);
+
+ if (num_percentiles == 0)
+ PG_RETURN_POINTER(construct_empty_array(datumtype));
+
+ result_datum = palloc0(num_percentiles * sizeof(Datum));
+ result_isnull = palloc0(num_percentiles * sizeof(bool));
+
+ pct_info = setup_pct_info(num_percentiles,
+ percentiles_datum,
+ percentiles_null,
+ rowcount,
+ true);
+
+ /*
+ * Start by dealing with any nulls in the param array - those are
+ * sorted to the front on row=0, so set the corresponding result
+ * indexes to null
+ */
+ for (i = 0; i < num_percentiles; ++i)
+ {
+ int idx = pct_info[i].idx;
+
+ if (pct_info[i].first_row > 0)
+ break;
+
+ result_datum[idx] = (Datum) 0;
+ result_isnull[idx] = true;
+ }
+
+ /*
+ * If there's anything left after doing the nulls, then grind the
+ * input and extract the needed values
+ */
+ if (i < num_percentiles)
+ {
+ tuplesort_performsort(sorter);
+
+ for (; i < num_percentiles; ++i)
+ {
+ int64 target_row = pct_info[i].first_row;
+ bool need_lerp = pct_info[i].second_row > target_row;
+ int idx = pct_info[i].idx;
+
+ if (target_row > rownum_second)
+ {
+ rownum = rownum_second;
+
+ while (target_row > ++rownum)
+ {
+ if (!tuplesort_getdatum(sorter, true, NULL, NULL))
+ elog(ERROR,"missing row in percentile_cont");
+ }
+
+ if (!tuplesort_getdatum(sorter, true, &first_val, &isnull) || isnull)
+ elog(ERROR,"missing row in percentile_cont");
+
+ rownum_second = rownum;
+
+ if (need_lerp)
+ {
+ if (!tuplesort_getdatum(sorter, true, &second_val, &isnull) || isnull)
+ elog(ERROR,"missing row in percentile_cont");
+ ++rownum_second;
+ }
+ }
+ else if (target_row == rownum_second)
+ {
+ first_val = second_val;
+ rownum = rownum_second;
+
+ if (need_lerp)
+ {
+ if (!tuplesort_getdatum(sorter, true, &second_val, &isnull) || isnull)
+ elog(ERROR,"missing row in percentile_cont");
+ ++rownum_second;
+ }
+ }
+
+ if (need_lerp)
+ {
+ result_datum[idx] = lerpfunc(first_val, second_val, pct_info[i].proportion);
+ }
+ else
+ result_datum[idx] = first_val;
+
+ result_isnull[idx] = false;
+ }
+ }
+
+ /* We make the output array the same shape as the input */
+
+ PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull,
+ ARR_NDIM(param),
+ ARR_DIMS(param), ARR_LBOUND(param),
+ expect_type,
+ typLen,
+ typByVal,
+ typAlign));
+ }
+
+
+ /*
+ * percentile_cont(float8[]) within group (float8) - continuous percentiles
+ */
+
+ Datum percentile_cont_float8_multi_final(PG_FUNCTION_ARGS);
+ Datum percentile_cont_interval_multi_final(PG_FUNCTION_ARGS);
+
+ Datum
+ percentile_cont_float8_multi_final(PG_FUNCTION_ARGS)
+ {
+ return percentile_cont_multi_final_common(fcinfo,
+ FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd',
+ float8_lerp);
+ }
+
+ /*
+ * percentile_cont(float8[]) within group (Interval) - continuous percentiles
+ */
+
+ Datum
+ percentile_cont_interval_multi_final(PG_FUNCTION_ARGS)
+ {
+ return percentile_cont_multi_final_common(fcinfo,
+ INTERVALOID, 16, false, 'd',
+ interval_lerp);
+ }
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 22,27 ****
--- 22,28 ----
#include "access/sysattr.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
+ #include "catalog/pg_aggregate.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
***************
*** 293,298 **** static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
--- 294,302 ----
static int print_function_arguments(StringInfo buf, HeapTuple proctup,
bool print_table_args, bool print_defaults);
static void print_function_rettype(StringInfo buf, HeapTuple proctup);
+ static void print_aggregate_arguments(StringInfo buf,
+ HeapTuple proctup, HeapTuple aggtup,
+ bool print_defaults);
static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
Bitmapset *rels_used);
static bool refname_is_unique(char *refname, deparse_namespace *dpns,
***************
*** 403,408 **** static char *generate_function_name(Oid funcid, int nargs,
--- 407,414 ----
static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
static text *string_to_text(char *str);
static char *flatten_reloptions(Oid relid);
+ static void get_aggstd_expr(Aggref *aggref, deparse_context *context);
+ static void get_ordset_expr(Aggref *aggref, deparse_context *context);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
***************
*** 2268,2273 **** print_function_arguments(StringInfo buf, HeapTuple proctup,
--- 2274,2422 ----
/*
+ * pg_get_aggregate_arguments
+ * Get a nicely-formatted list of arguments for an aggregate.
+ * This is everything that would go after the function name
+ * in CREATE AGGREGATE, _including_ the parens, because in the
+ * case of ordered set funcs, we emit the WITHIN GROUP clause
+ * too.
+ */
+ Datum
+ pg_get_aggregate_arguments(PG_FUNCTION_ARGS)
+ {
+ Oid funcid = PG_GETARG_OID(0);
+ StringInfoData buf;
+ HeapTuple proctup;
+ HeapTuple aggtup;
+
+ initStringInfo(&buf);
+
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(proctup))
+ elog(ERROR, "cache lookup failed for function %u", funcid);
+
+ aggtup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(aggtup))
+ elog(ERROR, "function %u is not an aggregate function", funcid);
+
+ (void) print_aggregate_arguments(&buf, proctup, aggtup, true);
+
+ ReleaseSysCache(aggtup);
+ ReleaseSysCache(proctup);
+
+ PG_RETURN_TEXT_P(string_to_text(buf.data));
+ }
+
+ /*
+ * pg_get_aggregate_identity_arguments
+ * Get a formatted list of arguments for an aggregate.
+ * This is everything that would go after the function name in
+ * ALTER AGGREGATE, etc. In particular, don't print defaults.
+ * Currently, this is identical to pg_get_aggregate_arguments,
+ * but if we ever allow defaults that will change.
+ */
+ Datum
+ pg_get_aggregate_identity_arguments(PG_FUNCTION_ARGS)
+ {
+ Oid funcid = PG_GETARG_OID(0);
+ StringInfoData buf;
+ HeapTuple proctup;
+ HeapTuple aggtup;
+
+ initStringInfo(&buf);
+
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(proctup))
+ elog(ERROR, "cache lookup failed for function %u", funcid);
+
+ aggtup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(aggtup))
+ elog(ERROR, "function %u is not an aggregate function", funcid);
+
+ (void) print_aggregate_arguments(&buf, proctup, aggtup, false);
+
+ ReleaseSysCache(aggtup);
+ ReleaseSysCache(proctup);
+
+ PG_RETURN_TEXT_P(string_to_text(buf.data));
+ }
+
+
+ /*
+ * Common code for pg_get_aggregate_arguments
+ * We print argument defaults only if print_defaults is true.
+ */
+ static void
+ print_aggregate_arguments(StringInfo buf,
+ HeapTuple proctup, HeapTuple aggtup,
+ bool print_defaults)
+ {
+ Form_pg_aggregate agg = (Form_pg_aggregate) GETSTRUCT(aggtup);
+ int numargs;
+ bool ordsetfunc = agg->aggisordsetfunc;
+ int numdirectargs = agg->aggordnargs;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ int i;
+
+ /* defaults not supported at this time */
+ (void) print_defaults;
+
+ numargs = get_func_arg_info(proctup,
+ &argtypes, &argnames, &argmodes);
+
+ appendStringInfoChar(buf, '(');
+
+ for (i = 0; i < numargs; i++)
+ {
+ Oid argtype = argtypes[i];
+ char *argname = argnames ? argnames[i] : NULL;
+ char argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
+ const char *modename;
+
+ switch (argmode)
+ {
+ case PROARGMODE_IN:
+ modename = "";
+ break;
+ case PROARGMODE_VARIADIC:
+ modename = "VARIADIC ";
+ break;
+ default:
+ elog(ERROR, "invalid parameter mode '%c'", argmode);
+ modename = NULL; /* keep compiler quiet */
+ break;
+ }
+
+ if (i == numdirectargs)
+ {
+ appendStringInfoString(buf, ") WITHIN GROUP (");
+ }
+ else if (i > 0)
+ appendStringInfoString(buf, ", ");
+
+ appendStringInfoString(buf, modename);
+
+ if (argname && argname[0])
+ appendStringInfo(buf, "%s ", quote_identifier(argname));
+
+ appendStringInfoString(buf, format_type_be(argtype));
+ }
+
+ if (ordsetfunc)
+ {
+ if (numdirectargs < 0 || numdirectargs == numargs)
+ appendStringInfoString(buf, ") WITHIN GROUP (*");
+ }
+ else if (numargs == 0)
+ appendStringInfoChar(buf, '*');
+
+ appendStringInfoChar(buf, ')');
+ }
+
+
+ /*
* deparse_expression - General utility for deparsing expressions
*
* calls deparse_expression_pretty with all prettyPrinting disabled
***************
*** 7408,7413 **** static void
--- 7557,7636 ----
get_agg_expr(Aggref *aggref, deparse_context *context)
{
StringInfo buf = context->buf;
+
+ if (aggref->isordset)
+ {
+ get_ordset_expr(aggref, context);
+ }
+ else
+ {
+ get_aggstd_expr(aggref, context);
+ }
+
+ if (aggref->aggfilter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)aggref->aggfilter, context, false);
+ }
+
+ appendStringInfoString(buf, ")");
+ }
+
+ static void
+ get_ordset_expr(Aggref *aggref, deparse_context *context)
+ {
+ StringInfo buf = context->buf;
+ Oid argtypes[FUNC_MAX_ARGS];
+ List *arglist;
+ int nargs;
+ ListCell *l;
+
+ arglist = NIL;
+ nargs = 0;
+
+ foreach(l, aggref->orddirectargs)
+ {
+ Node *arg = (Node *) lfirst(l);
+
+ Assert(!IsA(arg, NamedArgExpr));
+ if (nargs >= FUNC_MAX_ARGS) /* paranoia */
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ errmsg("too many arguments")));
+ argtypes[nargs] = exprType(arg);
+ nargs++;
+ }
+
+ /* For direct arguments in case of ordered set functions */
+ foreach(l, aggref->args)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+ Node *arg = (Node *) tle->expr;
+
+ Assert(!IsA(arg, NamedArgExpr));
+ if (nargs >= FUNC_MAX_ARGS) /* paranoia */
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ errmsg("too many arguments")));
+ argtypes[nargs] = exprType(arg);
+ arglist = lappend(arglist, arg);
+ nargs++;
+ }
+
+ appendStringInfo(buf, "%s(",
+ generate_function_name(aggref->aggfnoid, nargs,
+ NIL, argtypes,
+ false, NULL));
+
+ get_rule_expr((Node *)aggref->orddirectargs, context, true);
+ appendStringInfoString(buf, ") WITHIN GROUP (ORDER BY ");
+ get_rule_orderby(aggref->aggorder, aggref->args, false, context);
+
+ }
+ static void
+ get_aggstd_expr(Aggref *aggref, deparse_context *context)
+ {
+ StringInfo buf = context->buf;
Oid argtypes[FUNC_MAX_ARGS];
List *arglist;
int nargs;
***************
*** 7462,7475 **** get_agg_expr(Aggref *aggref, deparse_context *context)
appendStringInfoString(buf, " ORDER BY ");
get_rule_orderby(aggref->aggorder, aggref->args, false, context);
}
-
- if (aggref->aggfilter != NULL)
- {
- appendStringInfoString(buf, ") FILTER (WHERE ");
- get_rule_expr((Node *) aggref->aggfilter, context, false);
- }
-
- appendStringInfoChar(buf, ')');
}
/*
--- 7685,7690 ----
*** a/src/backend/utils/sort/tuplesort.c
--- b/src/backend/utils/sort/tuplesort.c
***************
*** 1411,1433 **** tuplesort_performsort(Tuplesortstate *state)
* Internal routine to fetch the next tuple in either forward or back
* direction into *stup. Returns FALSE if no more tuples.
* If *should_free is set, the caller must pfree stup.tuple when done with it.
*/
static bool
tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
SortTuple *stup, bool *should_free)
{
unsigned int tuplen;
switch (state->status)
{
case TSS_SORTEDINMEM:
Assert(forward || state->randomAccess);
! *should_free = false;
if (forward)
{
if (state->current < state->memtupcount)
{
! *stup = state->memtuples[state->current++];
return true;
}
state->eof_reached = true;
--- 1411,1439 ----
* Internal routine to fetch the next tuple in either forward or back
* direction into *stup. Returns FALSE if no more tuples.
* If *should_free is set, the caller must pfree stup.tuple when done with it.
+ * stup may be null to move without fetching.
*/
static bool
tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
SortTuple *stup, bool *should_free)
{
unsigned int tuplen;
+ SortTuple dummy;
+ SortTuple *ptup = stup ? stup : &dummy;
switch (state->status)
{
case TSS_SORTEDINMEM:
Assert(forward || state->randomAccess);
! if (should_free)
! *should_free = false;
if (forward)
{
if (state->current < state->memtupcount)
{
! if (stup)
! *stup = state->memtuples[state->current];
! state->current++;
return true;
}
state->eof_reached = true;
***************
*** 1459,1479 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
if (state->current <= 0)
return false;
}
! *stup = state->memtuples[state->current - 1];
return true;
}
break;
case TSS_SORTEDONTAPE:
Assert(forward || state->randomAccess);
! *should_free = true;
if (forward)
{
if (state->eof_reached)
return false;
if ((tuplen = getlen(state, state->result_tape, true)) != 0)
{
! READTUP(state, stup, state->result_tape, tuplen);
return true;
}
else
--- 1465,1489 ----
if (state->current <= 0)
return false;
}
! if (stup)
! *stup = state->memtuples[state->current - 1];
return true;
}
break;
case TSS_SORTEDONTAPE:
Assert(forward || state->randomAccess);
! if (should_free)
! *should_free = true;
if (forward)
{
if (state->eof_reached)
return false;
if ((tuplen = getlen(state, state->result_tape, true)) != 0)
{
! READTUP(state, ptup, state->result_tape, tuplen);
! if (!stup && dummy.tuple)
! pfree(dummy.tuple);
return true;
}
else
***************
*** 1546,1557 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
state->result_tape,
tuplen))
elog(ERROR, "bogus tuple length in backward scan");
! READTUP(state, stup, state->result_tape, tuplen);
return true;
case TSS_FINALMERGE:
Assert(forward);
! *should_free = true;
/*
* This code should match the inner loop of mergeonerun().
--- 1556,1570 ----
state->result_tape,
tuplen))
elog(ERROR, "bogus tuple length in backward scan");
! READTUP(state, ptup, state->result_tape, tuplen);
! if (!stup && dummy.tuple)
! pfree(dummy.tuple);
return true;
case TSS_FINALMERGE:
Assert(forward);
! if (should_free)
! *should_free = true;
/*
* This code should match the inner loop of mergeonerun().
***************
*** 1563,1573 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
int tupIndex;
SortTuple *newtup;
! *stup = state->memtuples[0];
/* returned tuple is no longer counted in our memory space */
! if (stup->tuple)
{
! tuplen = GetMemoryChunkSpace(stup->tuple);
state->availMem += tuplen;
state->mergeavailmem[srcTape] += tuplen;
}
--- 1576,1586 ----
int tupIndex;
SortTuple *newtup;
! *ptup = state->memtuples[0];
/* returned tuple is no longer counted in our memory space */
! if (ptup->tuple)
{
! tuplen = GetMemoryChunkSpace(ptup->tuple);
state->availMem += tuplen;
state->mergeavailmem[srcTape] += tuplen;
}
***************
*** 1598,1603 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
--- 1611,1618 ----
newtup->tupindex = state->mergefreelist;
state->mergefreelist = tupIndex;
state->mergeavailslots[srcTape]++;
+ if (!stup && dummy.tuple)
+ pfree(dummy.tuple);
return true;
}
return false;
***************
*** 1620,1639 **** tuplesort_gettupleslot(Tuplesortstate *state, bool forward,
MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext);
SortTuple stup;
bool should_free;
! if (!tuplesort_gettuple_common(state, forward, &stup, &should_free))
! stup.tuple = NULL;
MemoryContextSwitchTo(oldcontext);
! if (stup.tuple)
{
! ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free);
return true;
}
else
{
! ExecClearTuple(slot);
return false;
}
}
--- 1635,1656 ----
MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext);
SortTuple stup;
bool should_free;
+ bool found;
! found = tuplesort_gettuple_common(state, forward, (slot ? &stup : NULL), &should_free);
MemoryContextSwitchTo(oldcontext);
! if (found)
{
! if (slot)
! ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free);
return true;
}
else
{
! if (slot)
! ExecClearTuple(slot);
return false;
}
}
***************
*** 1692,1715 **** tuplesort_getdatum(Tuplesortstate *state, bool forward,
SortTuple stup;
bool should_free;
! if (!tuplesort_gettuple_common(state, forward, &stup, &should_free))
{
MemoryContextSwitchTo(oldcontext);
return false;
}
! if (stup.isnull1 || state->datumTypeByVal)
{
! *val = stup.datum1;
! *isNull = stup.isnull1;
! }
! else
! {
! if (should_free)
*val = stup.datum1;
else
! *val = datumCopy(stup.datum1, false, state->datumTypeLen);
! *isNull = false;
}
MemoryContextSwitchTo(oldcontext);
--- 1709,1735 ----
SortTuple stup;
bool should_free;
! if (!tuplesort_gettuple_common(state, forward, (val ? &stup : NULL), &should_free))
{
MemoryContextSwitchTo(oldcontext);
return false;
}
! if (val)
{
! if (stup.isnull1 || state->datumTypeByVal)
! {
*val = stup.datum1;
+ *isNull = stup.isnull1;
+ }
else
! {
! if (should_free)
! *val = stup.datum1;
! else
! *val = datumCopy(stup.datum1, false, state->datumTypeLen);
! *isNull = false;
! }
}
MemoryContextSwitchTo(oldcontext);
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 229,234 **** static void getTableData(TableInfo *tblinfo, int numTables, bool oids);
--- 229,235 ----
static void makeTableDataInfo(TableInfo *tbinfo, bool oids);
static void buildMatViewRefreshDependencies(Archive *fout);
static void getTableDataFKConstraints(void);
+ static char *format_aggregate_arguments(FuncInfo *finfo, char *funcargs);
static char *format_function_arguments(FuncInfo *finfo, char *funcargs,
bool is_agg);
static char *format_function_arguments_old(Archive *fout,
***************
*** 9459,9464 **** dumpProcLang(Archive *fout, ProcLangInfo *plang)
--- 9460,9481 ----
}
/*
+ * format_aggregate_arguments: generate function name and argument list
+ *
+ * This is used when we can rely on pg_get_aggregate_arguments to format
+ * the argument list.
+ */
+ static char *
+ format_aggregate_arguments(FuncInfo *finfo, char *funcargs)
+ {
+ PQExpBufferData fn;
+
+ initPQExpBuffer(&fn);
+ appendPQExpBuffer(&fn, "%s%s", fmtId(finfo->dobj.name), funcargs);
+ return fn.data;
+ }
+
+ /*
* format_function_arguments: generate function name and argument list
*
* This is used when we can rely on pg_get_function_arguments to format
***************
*** 11512,11517 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11529,11537 ----
int i_aggtransfn;
int i_aggfinalfn;
int i_aggsortop;
+ int i_aggtranssortop;
+ int i_hypothetical;
+ int i_isstrict;
int i_aggtranstype;
int i_aggtransspace;
int i_agginitval;
***************
*** 11519,11528 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11539,11552 ----
const char *aggtransfn;
const char *aggfinalfn;
const char *aggsortop;
+ const char *aggtranssortop;
const char *aggtranstype;
const char *aggtransspace;
const char *agginitval;
+ bool hypothetical;
+ bool isstrict;
bool convertok;
+ bool has_comma = false;
/* Skip if not to be dumped */
if (!agginfo->aggfn.dobj.dump || dataOnly)
***************
*** 11543,11552 **** dumpAgg(Archive *fout, AggInfo *agginfo)
appendPQExpBuffer(query, "SELECT aggtransfn, "
"aggfinalfn, aggtranstype::pg_catalog.regtype, "
"aggsortop::pg_catalog.regoperator, "
"aggtransspace, agginitval, "
"'t'::boolean AS convertok, "
! "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs, "
! "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs "
"FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
"WHERE a.aggfnoid = p.oid "
"AND p.oid = '%u'::pg_catalog.oid",
--- 11567,11579 ----
appendPQExpBuffer(query, "SELECT aggtransfn, "
"aggfinalfn, aggtranstype::pg_catalog.regtype, "
"aggsortop::pg_catalog.regoperator, "
+ "aggtranssortop::pg_catalog.regoperator, "
+ "(aggordnargs = -2) as hypothetical, "
+ "p.proisstrict as isstrict, "
"aggtransspace, agginitval, "
"'t'::boolean AS convertok, "
! "pg_catalog.pg_get_aggregate_arguments(p.oid) AS funcargs, "
! "pg_catalog.pg_get_aggregate_identity_arguments(p.oid) AS funciargs "
"FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
"WHERE a.aggfnoid = p.oid "
"AND p.oid = '%u'::pg_catalog.oid",
***************
*** 11557,11562 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11584,11592 ----
appendPQExpBuffer(query, "SELECT aggtransfn, "
"aggfinalfn, aggtranstype::pg_catalog.regtype, "
"aggsortop::pg_catalog.regoperator, "
+ "0 as aggtranssortop, "
+ "false as hypothetical, "
+ "false as isstrict, "
"0 AS aggtransspace, agginitval, "
"'t'::boolean AS convertok, "
"pg_catalog.pg_get_function_arguments(p.oid) AS funcargs, "
***************
*** 11571,11576 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11601,11609 ----
appendPQExpBuffer(query, "SELECT aggtransfn, "
"aggfinalfn, aggtranstype::pg_catalog.regtype, "
"aggsortop::pg_catalog.regoperator, "
+ "0 as aggtranssortop, "
+ "false as hypothetical, "
+ "false as isstrict, "
"0 AS aggtransspace, agginitval, "
"'t'::boolean AS convertok "
"FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
***************
*** 11583,11588 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11616,11624 ----
appendPQExpBuffer(query, "SELECT aggtransfn, "
"aggfinalfn, aggtranstype::pg_catalog.regtype, "
"0 AS aggsortop, "
+ "0 as aggtranssortop, "
+ "'f'::boolean as hypothetical, "
+ "'f'::boolean as isstrict, "
"0 AS aggtransspace, agginitval, "
"'t'::boolean AS convertok "
"FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
***************
*** 11595,11600 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11631,11639 ----
appendPQExpBuffer(query, "SELECT aggtransfn, aggfinalfn, "
"format_type(aggtranstype, NULL) AS aggtranstype, "
"0 AS aggsortop, "
+ "0 as aggtranssortop, "
+ "'f'::boolean as hypothetical, "
+ "'f'::boolean as isstrict, "
"0 AS aggtransspace, agginitval, "
"'t'::boolean AS convertok "
"FROM pg_aggregate "
***************
*** 11607,11612 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11646,11654 ----
"aggfinalfn, "
"(SELECT typname FROM pg_type WHERE oid = aggtranstype1) AS aggtranstype, "
"0 AS aggsortop, "
+ "0 as aggtranssortop, "
+ "'f'::boolean as hypothetical, "
+ "'f'::boolean as isstrict, "
"0 AS aggtransspace, agginitval1 AS agginitval, "
"(aggtransfn2 = 0 and aggtranstype2 = 0 and agginitval2 is null) AS convertok "
"FROM pg_aggregate "
***************
*** 11619,11624 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11661,11669 ----
i_aggtransfn = PQfnumber(res, "aggtransfn");
i_aggfinalfn = PQfnumber(res, "aggfinalfn");
i_aggsortop = PQfnumber(res, "aggsortop");
+ i_aggtranssortop = PQfnumber(res, "aggtranssortop");
+ i_hypothetical = PQfnumber(res, "hypothetical");
+ i_isstrict = PQfnumber(res, "isstrict");
i_aggtranstype = PQfnumber(res, "aggtranstype");
i_aggtransspace = PQfnumber(res, "aggtransspace");
i_agginitval = PQfnumber(res, "agginitval");
***************
*** 11627,11638 **** dumpAgg(Archive *fout, AggInfo *agginfo)
aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
aggsortop = PQgetvalue(res, 0, i_aggsortop);
aggtranstype = PQgetvalue(res, 0, i_aggtranstype);
aggtransspace = PQgetvalue(res, 0, i_aggtransspace);
agginitval = PQgetvalue(res, 0, i_agginitval);
convertok = (PQgetvalue(res, 0, i_convertok)[0] == 't');
! if (fout->remoteVersion >= 80400)
{
/* 8.4 or later; we rely on server-side code for most of the work */
char *funcargs;
--- 11672,11697 ----
aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
aggsortop = PQgetvalue(res, 0, i_aggsortop);
+ aggtranssortop = PQgetvalue(res, 0, i_aggtranssortop);
+ hypothetical = (PQgetvalue(res, 0, i_hypothetical)[0] == 't');
+ isstrict = (PQgetvalue(res, 0, i_isstrict)[0] == 't');
aggtranstype = PQgetvalue(res, 0, i_aggtranstype);
aggtransspace = PQgetvalue(res, 0, i_aggtransspace);
agginitval = PQgetvalue(res, 0, i_agginitval);
convertok = (PQgetvalue(res, 0, i_convertok)[0] == 't');
! if (fout->remoteVersion >= 90400)
! {
! /* 9.4 or later; we rely on server-side code for almost all of the work */
! char *funcargs;
! char *funciargs;
!
! funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs"));
! funciargs = PQgetvalue(res, 0, PQfnumber(res, "funciargs"));
! aggfullsig = format_aggregate_arguments(&agginfo->aggfn, funcargs);
! aggsig = format_aggregate_arguments(&agginfo->aggfn, funciargs);
! }
! else if (fout->remoteVersion >= 80400)
{
/* 8.4 or later; we rely on server-side code for most of the work */
char *funcargs;
***************
*** 11662,11705 **** dumpAgg(Archive *fout, AggInfo *agginfo)
if (fout->remoteVersion >= 70300)
{
/* If using 7.3's regproc or regtype, data is already quoted */
! appendPQExpBuffer(details, " SFUNC = %s,\n STYPE = %s",
! aggtransfn,
! aggtranstype);
}
else if (fout->remoteVersion >= 70100)
{
/* format_type quotes, regproc does not */
! appendPQExpBuffer(details, " SFUNC = %s,\n STYPE = %s",
fmtId(aggtransfn),
aggtranstype);
}
else
{
/* need quotes all around */
! appendPQExpBuffer(details, " SFUNC = %s,\n",
fmtId(aggtransfn));
appendPQExpBuffer(details, " STYPE = %s",
fmtId(aggtranstype));
}
if (strcmp(aggtransspace, "0") != 0)
{
appendPQExpBuffer(details, ",\n SSPACE = %s",
aggtransspace);
}
if (!PQgetisnull(res, 0, i_agginitval))
{
appendPQExpBufferStr(details, ",\n INITCOND = ");
appendStringLiteralAH(details, agginitval, fout);
}
- if (strcmp(aggfinalfn, "-") != 0)
- {
- appendPQExpBuffer(details, ",\n FINALFUNC = %s",
- aggfinalfn);
- }
-
aggsortop = convertOperatorReference(fout, aggsortop);
if (aggsortop)
{
--- 11721,11786 ----
if (fout->remoteVersion >= 70300)
{
/* If using 7.3's regproc or regtype, data is already quoted */
! /*
! * either or both of SFUNC and STYPE might be missing in >90400,
! * but if SFUNC is missing, then FINALFUNC will always be present,
! * and if SFUNC is present then STYPE must also be present; the
! * code below relies on these conditions to keep the commas in the
! * right places. STRICT must be forced to false if SFUNC is present.
! */
!
! if (strcmp(aggtransfn,"-") != 0)
! {
! appendPQExpBuffer(details, "\n SFUNC = %s,", aggtransfn);
! isstrict = false;
! }
!
! if (strcmp(aggtranstype,"-") != 0)
! appendPQExpBuffer(details, "\n STYPE = %s", aggtranstype);
! else
! has_comma = true;
}
else if (fout->remoteVersion >= 70100)
{
/* format_type quotes, regproc does not */
! appendPQExpBuffer(details, "\n SFUNC = %s,\n STYPE = %s",
fmtId(aggtransfn),
aggtranstype);
}
else
{
/* need quotes all around */
! appendPQExpBuffer(details, "\n SFUNC = %s,\n",
fmtId(aggtransfn));
appendPQExpBuffer(details, " STYPE = %s",
fmtId(aggtranstype));
}
+ if (strcmp(aggfinalfn, "-") != 0)
+ {
+ appendPQExpBuffer(details, "%s\n FINALFUNC = %s",
+ (has_comma ? "" : ","),
+ aggfinalfn);
+ }
+
if (strcmp(aggtransspace, "0") != 0)
{
appendPQExpBuffer(details, ",\n SSPACE = %s",
aggtransspace);
}
+ if (hypothetical)
+ appendPQExpBufferStr(details, ",\n HYPOTHETICAL");
+
+ if (isstrict)
+ appendPQExpBufferStr(details, ",\n STRICT");
+
if (!PQgetisnull(res, 0, i_agginitval))
{
appendPQExpBufferStr(details, ",\n INITCOND = ");
appendStringLiteralAH(details, agginitval, fout);
}
aggsortop = convertOperatorReference(fout, aggsortop);
if (aggsortop)
{
***************
*** 11707,11712 **** dumpAgg(Archive *fout, AggInfo *agginfo)
--- 11788,11800 ----
aggsortop);
}
+ aggtranssortop = convertOperatorReference(fout, aggtranssortop);
+ if (aggtranssortop)
+ {
+ appendPQExpBuffer(details, ",\n TRANSSORTOP = %s",
+ aggtranssortop);
+ }
+
/*
* DROP must be fully qualified in case same name appears in pg_catalog
*/
***************
*** 11714,11720 **** dumpAgg(Archive *fout, AggInfo *agginfo)
fmtId(agginfo->aggfn.dobj.namespace->dobj.name),
aggsig);
! appendPQExpBuffer(q, "CREATE AGGREGATE %s (\n%s\n);\n",
aggfullsig, details->data);
appendPQExpBuffer(labelq, "AGGREGATE %s", aggsig);
--- 11802,11808 ----
fmtId(agginfo->aggfn.dobj.namespace->dobj.name),
aggsig);
! appendPQExpBuffer(q, "CREATE AGGREGATE %s (%s\n);\n",
aggfullsig, details->data);
appendPQExpBuffer(labelq, "AGGREGATE %s", aggsig);
***************
*** 11743,11749 **** dumpAgg(Archive *fout, AggInfo *agginfo)
/*
* Since there is no GRANT ON AGGREGATE syntax, we have to make the ACL
* command look like a function's GRANT; in particular this affects the
! * syntax for zero-argument aggregates.
*/
free(aggsig);
free(aggsig_tag);
--- 11831,11837 ----
/*
* Since there is no GRANT ON AGGREGATE syntax, we have to make the ACL
* command look like a function's GRANT; in particular this affects the
! * syntax for zero-argument aggregates and ordered set functions.
*/
free(aggsig);
free(aggsig_tag);
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 72,78 **** describeAggregates(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Name"),
gettext_noop("Result data type"));
! if (pset.sversion >= 80400)
appendPQExpBuffer(&buf,
" CASE WHEN p.pronargs = 0\n"
" THEN CAST('*' AS pg_catalog.text)\n"
--- 72,82 ----
gettext_noop("Name"),
gettext_noop("Result data type"));
! if (pset.sversion >= 90400)
! appendPQExpBuffer(&buf,
! " pg_catalog.pg_get_aggregate_arguments(p.oid) AS \"%s\",\n",
! gettext_noop("Argument data types"));
! else if (pset.sversion >= 80400)
appendPQExpBuffer(&buf,
" CASE WHEN p.pronargs = 0\n"
" THEN CAST('*' AS pg_catalog.text)\n"
***************
*** 254,260 **** describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
gettext_noop("Schema"),
gettext_noop("Name"));
! if (pset.sversion >= 80400)
appendPQExpBuffer(&buf,
" pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n"
" pg_catalog.pg_get_function_arguments(p.oid) as \"%s\",\n"
--- 258,283 ----
gettext_noop("Schema"),
gettext_noop("Name"));
! if (pset.sversion >= 90400)
! appendPQExpBuffer(&buf,
! " pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n"
! " CASE WHEN p.proisagg THEN pg_catalog.pg_get_aggregate_arguments(p.oid)\n"
! " ELSE pg_catalog.pg_get_function_arguments(p.oid) END as \"%s\",\n"
! " CASE\n"
! " WHEN p.proisagg THEN '%s'\n"
! " WHEN p.proiswindow THEN '%s'\n"
! " WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN '%s'\n"
! " ELSE '%s'\n"
! " END as \"%s\"",
! gettext_noop("Result data type"),
! gettext_noop("Argument data types"),
! /* translator: "agg" is short for "aggregate" */
! gettext_noop("agg"),
! gettext_noop("window"),
! gettext_noop("trigger"),
! gettext_noop("normal"),
! gettext_noop("Type"));
! else if (pset.sversion >= 80400)
appendPQExpBuffer(&buf,
" pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n"
" pg_catalog.pg_get_function_arguments(p.oid) as \"%s\",\n"
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
***************
*** 33,38 ****
--- 33,41 ----
* aggsortop associated sort operator (0 if none)
* aggtranstype type of aggregate's transition (state) data
* aggtransspace estimated size of state data (0 for default estimate)
+ * aggtranssortop An optional sort operator for the type aggtranstype
+ * aggordnargs Number of direct arguments to aggregate.
+ * aggisordsetfunc A flag to represent whether a function is ordered set or not
* agginitval initial value for transition state (can be NULL)
* ----------------------------------------------------------------
*/
***************
*** 46,51 **** CATALOG(pg_aggregate,2600) BKI_WITHOUT_OIDS
--- 49,57 ----
Oid aggsortop;
Oid aggtranstype;
int32 aggtransspace;
+ Oid aggtranssortop;
+ int32 aggordnargs;
+ bool aggisordsetfunc;
#ifdef CATALOG_VARLEN /* variable-length fields start here */
text agginitval;
***************
*** 64,77 **** typedef FormData_pg_aggregate *Form_pg_aggregate;
* ----------------
*/
! #define Natts_pg_aggregate 7
#define Anum_pg_aggregate_aggfnoid 1
#define Anum_pg_aggregate_aggtransfn 2
#define Anum_pg_aggregate_aggfinalfn 3
#define Anum_pg_aggregate_aggsortop 4
#define Anum_pg_aggregate_aggtranstype 5
#define Anum_pg_aggregate_aggtransspace 6
! #define Anum_pg_aggregate_agginitval 7
/* ----------------
--- 70,86 ----
* ----------------
*/
! #define Natts_pg_aggregate 10
#define Anum_pg_aggregate_aggfnoid 1
#define Anum_pg_aggregate_aggtransfn 2
#define Anum_pg_aggregate_aggfinalfn 3
#define Anum_pg_aggregate_aggsortop 4
#define Anum_pg_aggregate_aggtranstype 5
#define Anum_pg_aggregate_aggtransspace 6
! #define Anum_pg_aggregate_aggtranssortop 7
! #define Anum_pg_aggregate_aggordnargs 8
! #define Anum_pg_aggregate_aggisordsetfunc 9
! #define Anum_pg_aggregate_agginitval 10
/* ----------------
***************
*** 80,242 **** typedef FormData_pg_aggregate *Form_pg_aggregate;
*/
/* avg */
! DATA(insert ( 2100 int8_avg_accum numeric_avg 0 2281 128 _null_ ));
! DATA(insert ( 2101 int4_avg_accum int8_avg 0 1016 0 "{0,0}" ));
! DATA(insert ( 2102 int2_avg_accum int8_avg 0 1016 0 "{0,0}" ));
! DATA(insert ( 2103 numeric_avg_accum numeric_avg 0 2281 128 _null_ ));
! DATA(insert ( 2104 float4_accum float8_avg 0 1022 0 "{0,0,0}" ));
! DATA(insert ( 2105 float8_accum float8_avg 0 1022 0 "{0,0,0}" ));
! DATA(insert ( 2106 interval_accum interval_avg 0 1187 0 "{0 second,0 second}" ));
/* sum */
! DATA(insert ( 2107 int8_avg_accum numeric_sum 0 2281 128 _null_ ));
! DATA(insert ( 2108 int4_sum - 0 20 0 _null_ ));
! DATA(insert ( 2109 int2_sum - 0 20 0 _null_ ));
! DATA(insert ( 2110 float4pl - 0 700 0 _null_ ));
! DATA(insert ( 2111 float8pl - 0 701 0 _null_ ));
! DATA(insert ( 2112 cash_pl - 0 790 0 _null_ ));
! DATA(insert ( 2113 interval_pl - 0 1186 0 _null_ ));
! DATA(insert ( 2114 numeric_avg_accum numeric_sum 0 2281 128 _null_ ));
/* max */
! DATA(insert ( 2115 int8larger - 413 20 0 _null_ ));
! DATA(insert ( 2116 int4larger - 521 23 0 _null_ ));
! DATA(insert ( 2117 int2larger - 520 21 0 _null_ ));
! DATA(insert ( 2118 oidlarger - 610 26 0 _null_ ));
! DATA(insert ( 2119 float4larger - 623 700 0 _null_ ));
! DATA(insert ( 2120 float8larger - 674 701 0 _null_ ));
! DATA(insert ( 2121 int4larger - 563 702 0 _null_ ));
! DATA(insert ( 2122 date_larger - 1097 1082 0 _null_ ));
! DATA(insert ( 2123 time_larger - 1112 1083 0 _null_ ));
! DATA(insert ( 2124 timetz_larger - 1554 1266 0 _null_ ));
! DATA(insert ( 2125 cashlarger - 903 790 0 _null_ ));
! DATA(insert ( 2126 timestamp_larger - 2064 1114 0 _null_ ));
! DATA(insert ( 2127 timestamptz_larger - 1324 1184 0 _null_ ));
! DATA(insert ( 2128 interval_larger - 1334 1186 0 _null_ ));
! DATA(insert ( 2129 text_larger - 666 25 0 _null_ ));
! DATA(insert ( 2130 numeric_larger - 1756 1700 0 _null_ ));
! DATA(insert ( 2050 array_larger - 1073 2277 0 _null_ ));
! DATA(insert ( 2244 bpchar_larger - 1060 1042 0 _null_ ));
! DATA(insert ( 2797 tidlarger - 2800 27 0 _null_ ));
! DATA(insert ( 3526 enum_larger - 3519 3500 0 _null_ ));
/* min */
! DATA(insert ( 2131 int8smaller - 412 20 0 _null_ ));
! DATA(insert ( 2132 int4smaller - 97 23 0 _null_ ));
! DATA(insert ( 2133 int2smaller - 95 21 0 _null_ ));
! DATA(insert ( 2134 oidsmaller - 609 26 0 _null_ ));
! DATA(insert ( 2135 float4smaller - 622 700 0 _null_ ));
! DATA(insert ( 2136 float8smaller - 672 701 0 _null_ ));
! DATA(insert ( 2137 int4smaller - 562 702 0 _null_ ));
! DATA(insert ( 2138 date_smaller - 1095 1082 0 _null_ ));
! DATA(insert ( 2139 time_smaller - 1110 1083 0 _null_ ));
! DATA(insert ( 2140 timetz_smaller - 1552 1266 0 _null_ ));
! DATA(insert ( 2141 cashsmaller - 902 790 0 _null_ ));
! DATA(insert ( 2142 timestamp_smaller - 2062 1114 0 _null_ ));
! DATA(insert ( 2143 timestamptz_smaller - 1322 1184 0 _null_ ));
! DATA(insert ( 2144 interval_smaller - 1332 1186 0 _null_ ));
! DATA(insert ( 2145 text_smaller - 664 25 0 _null_ ));
! DATA(insert ( 2146 numeric_smaller - 1754 1700 0 _null_ ));
! DATA(insert ( 2051 array_smaller - 1072 2277 0 _null_ ));
! DATA(insert ( 2245 bpchar_smaller - 1058 1042 0 _null_ ));
! DATA(insert ( 2798 tidsmaller - 2799 27 0 _null_ ));
! DATA(insert ( 3527 enum_smaller - 3518 3500 0 _null_ ));
/* count */
! DATA(insert ( 2147 int8inc_any - 0 20 0 "0" ));
! DATA(insert ( 2803 int8inc - 0 20 0 "0" ));
/* var_pop */
! DATA(insert ( 2718 int8_accum numeric_var_pop 0 2281 128 _null_ ));
! DATA(insert ( 2719 int4_accum numeric_var_pop 0 2281 128 _null_ ));
! DATA(insert ( 2720 int2_accum numeric_var_pop 0 2281 128 _null_ ));
! DATA(insert ( 2721 float4_accum float8_var_pop 0 1022 0 "{0,0,0}" ));
! DATA(insert ( 2722 float8_accum float8_var_pop 0 1022 0 "{0,0,0}" ));
! DATA(insert ( 2723 numeric_accum numeric_var_pop 0 2281 128 _null_ ));
/* var_samp */
! DATA(insert ( 2641 int8_accum numeric_var_samp 0 2281 128 _null_ ));
! DATA(insert ( 2642 int4_accum numeric_var_samp 0 2281 128 _null_ ));
! DATA(insert ( 2643 int2_accum numeric_var_samp 0 2281 128 _null_ ));
! DATA(insert ( 2644 float4_accum float8_var_samp 0 1022 0 "{0,0,0}" ));
! DATA(insert ( 2645 float8_accum float8_var_samp 0 1022 0 "{0,0,0}" ));
! DATA(insert ( 2646 numeric_accum numeric_var_samp 0 2281 128 _null_ ));
/* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148 int8_accum numeric_var_samp 0 2281 128 _null_ ));
! DATA(insert ( 2149 int4_accum numeric_var_samp 0 2281 128 _null_ ));
! DATA(insert ( 2150 int2_accum numeric_var_samp 0 2281 128 _null_ ));
! DATA(insert ( 2151 float4_accum float8_var_samp 0 1022 0 "{0,0,0}" ));
! DATA(insert ( 2152 float8_accum float8_var_samp 0 1022 0 "{0,0,0}" ));
! DATA(insert ( 2153 numeric_accum numeric_var_samp 0 2281 128 _null_ ));
/* stddev_pop */
! DATA(insert ( 2724 int8_accum numeric_stddev_pop 0 2281 128 _null_ ));
! DATA(insert ( 2725 int4_accum numeric_stddev_pop 0 2281 128 _null_ ));
! DATA(insert ( 2726 int2_accum numeric_stddev_pop 0 2281 128 _null_ ));
! DATA(insert ( 2727 float4_accum float8_stddev_pop 0 1022 0 "{0,0,0}" ));
! DATA(insert ( 2728 float8_accum float8_stddev_pop 0 1022 0 "{0,0,0}" ));
! DATA(insert ( 2729 numeric_accum numeric_stddev_pop 0 2281 128 _null_ ));
/* stddev_samp */
! DATA(insert ( 2712 int8_accum numeric_stddev_samp 0 2281 128 _null_ ));
! DATA(insert ( 2713 int4_accum numeric_stddev_samp 0 2281 128 _null_ ));
! DATA(insert ( 2714 int2_accum numeric_stddev_samp 0 2281 128 _null_ ));
! DATA(insert ( 2715 float4_accum float8_stddev_samp 0 1022 0 "{0,0,0}" ));
! DATA(insert ( 2716 float8_accum float8_stddev_samp 0 1022 0 "{0,0,0}" ));
! DATA(insert ( 2717 numeric_accum numeric_stddev_samp 0 2281 128 _null_ ));
/* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154 int8_accum numeric_stddev_samp 0 2281 128 _null_ ));
! DATA(insert ( 2155 int4_accum numeric_stddev_samp 0 2281 128 _null_ ));
! DATA(insert ( 2156 int2_accum numeric_stddev_samp 0 2281 128 _null_ ));
! DATA(insert ( 2157 float4_accum float8_stddev_samp 0 1022 0 "{0,0,0}" ));
! DATA(insert ( 2158 float8_accum float8_stddev_samp 0 1022 0 "{0,0,0}" ));
! DATA(insert ( 2159 numeric_accum numeric_stddev_samp 0 2281 128 _null_ ));
/* SQL2003 binary regression aggregates */
! DATA(insert ( 2818 int8inc_float8_float8 - 0 20 0 "0" ));
! DATA(insert ( 2819 float8_regr_accum float8_regr_sxx 0 1022 0 "{0,0,0,0,0,0}" ));
! DATA(insert ( 2820 float8_regr_accum float8_regr_syy 0 1022 0 "{0,0,0,0,0,0}" ));
! DATA(insert ( 2821 float8_regr_accum float8_regr_sxy 0 1022 0 "{0,0,0,0,0,0}" ));
! DATA(insert ( 2822 float8_regr_accum float8_regr_avgx 0 1022 0 "{0,0,0,0,0,0}" ));
! DATA(insert ( 2823 float8_regr_accum float8_regr_avgy 0 1022 0 "{0,0,0,0,0,0}" ));
! DATA(insert ( 2824 float8_regr_accum float8_regr_r2 0 1022 0 "{0,0,0,0,0,0}" ));
! DATA(insert ( 2825 float8_regr_accum float8_regr_slope 0 1022 0 "{0,0,0,0,0,0}" ));
! DATA(insert ( 2826 float8_regr_accum float8_regr_intercept 0 1022 0 "{0,0,0,0,0,0}" ));
! DATA(insert ( 2827 float8_regr_accum float8_covar_pop 0 1022 0 "{0,0,0,0,0,0}" ));
! DATA(insert ( 2828 float8_regr_accum float8_covar_samp 0 1022 0 "{0,0,0,0,0,0}" ));
! DATA(insert ( 2829 float8_regr_accum float8_corr 0 1022 0 "{0,0,0,0,0,0}" ));
/* boolean-and and boolean-or */
! DATA(insert ( 2517 booland_statefunc - 58 16 0 _null_ ));
! DATA(insert ( 2518 boolor_statefunc - 59 16 0 _null_ ));
! DATA(insert ( 2519 booland_statefunc - 58 16 0 _null_ ));
/* bitwise integer */
! DATA(insert ( 2236 int2and - 0 21 0 _null_ ));
! DATA(insert ( 2237 int2or - 0 21 0 _null_ ));
! DATA(insert ( 2238 int4and - 0 23 0 _null_ ));
! DATA(insert ( 2239 int4or - 0 23 0 _null_ ));
! DATA(insert ( 2240 int8and - 0 20 0 _null_ ));
! DATA(insert ( 2241 int8or - 0 20 0 _null_ ));
! DATA(insert ( 2242 bitand - 0 1560 0 _null_ ));
! DATA(insert ( 2243 bitor - 0 1560 0 _null_ ));
/* xml */
! DATA(insert ( 2901 xmlconcat2 - 0 142 0 _null_ ));
/* array */
! DATA(insert ( 2335 array_agg_transfn array_agg_finalfn 0 2281 0 _null_ ));
/* text */
! DATA(insert ( 3538 string_agg_transfn string_agg_finalfn 0 2281 0 _null_ ));
/* bytea */
! DATA(insert ( 3545 bytea_string_agg_transfn bytea_string_agg_finalfn 0 2281 0 _null_ ));
/* json */
! DATA(insert ( 3175 json_agg_transfn json_agg_finalfn 0 2281 0 _null_ ));
/*
* prototypes for functions in pg_aggregate.c
--- 89,264 ----
*/
/* avg */
! DATA(insert ( 2100 int8_avg_accum numeric_avg 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2101 int4_avg_accum int8_avg 0 1016 0 0 -1 f "{0,0}" ));
! DATA(insert ( 2102 int2_avg_accum int8_avg 0 1016 0 0 -1 f "{0,0}" ));
! DATA(insert ( 2103 numeric_avg_accum numeric_avg 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2104 float4_accum float8_avg 0 1022 0 0 -1 f "{0,0,0}" ));
! DATA(insert ( 2105 float8_accum float8_avg 0 1022 0 0 -1 f "{0,0,0}" ));
! DATA(insert ( 2106 interval_accum interval_avg 0 1187 0 0 -1 f "{0 second,0 second}" ));
/* sum */
! DATA(insert ( 2107 int8_avg_accum numeric_sum 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2108 int4_sum - 0 20 0 0 -1 f _null_ ));
! DATA(insert ( 2109 int2_sum - 0 20 0 0 -1 f _null_ ));
! DATA(insert ( 2110 float4pl - 0 700 0 0 -1 f _null_ ));
! DATA(insert ( 2111 float8pl - 0 701 0 0 -1 f _null_ ));
! DATA(insert ( 2112 cash_pl - 0 790 0 0 -1 f _null_ ));
! DATA(insert ( 2113 interval_pl - 0 1186 0 0 -1 f _null_ ));
! DATA(insert ( 2114 numeric_avg_accum numeric_sum 0 2281 128 0 -1 f _null_ ));
/* max */
! DATA(insert ( 2115 int8larger - 413 20 0 0 -1 f _null_ ));
! DATA(insert ( 2116 int4larger - 521 23 0 0 -1 f _null_ ));
! DATA(insert ( 2117 int2larger - 520 21 0 0 -1 f _null_ ));
! DATA(insert ( 2118 oidlarger - 610 26 0 0 -1 f _null_ ));
! DATA(insert ( 2119 float4larger - 623 700 0 0 -1 f _null_ ));
! DATA(insert ( 2120 float8larger - 674 701 0 0 -1 f _null_ ));
! DATA(insert ( 2121 int4larger - 563 702 0 0 -1 f _null_ ));
! DATA(insert ( 2122 date_larger - 1097 1082 0 0 -1 f _null_ ));
! DATA(insert ( 2123 time_larger - 1112 1083 0 0 -1 f _null_ ));
! DATA(insert ( 2124 timetz_larger - 1554 1266 0 0 -1 f _null_ ));
! DATA(insert ( 2125 cashlarger - 903 790 0 0 -1 f _null_ ));
! DATA(insert ( 2126 timestamp_larger - 2064 1114 0 0 -1 f _null_ ));
! DATA(insert ( 2127 timestamptz_larger - 1324 1184 0 0 -1 f _null_ ));
! DATA(insert ( 2128 interval_larger - 1334 1186 0 0 -1 f _null_ ));
! DATA(insert ( 2129 text_larger - 666 25 0 0 -1 f _null_ ));
! DATA(insert ( 2130 numeric_larger - 1756 1700 0 0 -1 f _null_ ));
! DATA(insert ( 2050 array_larger - 1073 2277 0 0 -1 f _null_ ));
! DATA(insert ( 2244 bpchar_larger - 1060 1042 0 0 -1 f _null_ ));
! DATA(insert ( 2797 tidlarger - 2800 27 0 0 -1 f _null_ ));
! DATA(insert ( 3526 enum_larger - 3519 3500 0 0 -1 f _null_ ));
/* min */
! DATA(insert ( 2131 int8smaller - 412 20 0 0 -1 f _null_ ));
! DATA(insert ( 2132 int4smaller - 97 23 0 0 -1 f _null_ ));
! DATA(insert ( 2133 int2smaller - 95 21 0 0 -1 f _null_ ));
! DATA(insert ( 2134 oidsmaller - 609 26 0 0 -1 f _null_ ));
! DATA(insert ( 2135 float4smaller - 622 700 0 0 -1 f _null_ ));
! DATA(insert ( 2136 float8smaller - 672 701 0 0 -1 f _null_ ));
! DATA(insert ( 2137 int4smaller - 562 702 0 0 -1 f _null_ ));
! DATA(insert ( 2138 date_smaller - 1095 1082 0 0 -1 f _null_ ));
! DATA(insert ( 2139 time_smaller - 1110 1083 0 0 -1 f _null_ ));
! DATA(insert ( 2140 timetz_smaller - 1552 1266 0 0 -1 f _null_ ));
! DATA(insert ( 2141 cashsmaller - 902 790 0 0 -1 f _null_ ));
! DATA(insert ( 2142 timestamp_smaller - 2062 1114 0 0 -1 f _null_ ));
! DATA(insert ( 2143 timestamptz_smaller - 1322 1184 0 0 -1 f _null_ ));
! DATA(insert ( 2144 interval_smaller - 1332 1186 0 0 -1 f _null_ ));
! DATA(insert ( 2145 text_smaller - 664 25 0 0 -1 f _null_ ));
! DATA(insert ( 2146 numeric_smaller - 1754 1700 0 0 -1 f _null_ ));
! DATA(insert ( 2051 array_smaller - 1072 2277 0 0 -1 f _null_ ));
! DATA(insert ( 2245 bpchar_smaller - 1058 1042 0 0 -1 f _null_ ));
! DATA(insert ( 2798 tidsmaller - 2799 27 0 0 -1 f _null_ ));
! DATA(insert ( 3527 enum_smaller - 3518 3500 0 0 -1 f _null_ ));
/* count */
! DATA(insert ( 2147 int8inc_any - 0 20 0 0 -1 f "0" ));
! DATA(insert ( 2803 int8inc - 0 20 0 0 -1 f "0" ));
/* var_pop */
! DATA(insert ( 2718 int8_accum numeric_var_pop 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2719 int4_accum numeric_var_pop 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2720 int2_accum numeric_var_pop 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2721 float4_accum float8_var_pop 0 1022 0 0 -1 f "{0,0,0}" ));
! DATA(insert ( 2722 float8_accum float8_var_pop 0 1022 0 0 -1 f "{0,0,0}" ));
! DATA(insert ( 2723 numeric_accum numeric_var_pop 0 2281 128 0 -1 f _null_ ));
/* var_samp */
! DATA(insert ( 2641 int8_accum numeric_var_samp 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2642 int4_accum numeric_var_samp 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2643 int2_accum numeric_var_samp 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2644 float4_accum float8_var_samp 0 1022 0 0 -1 f "{0,0,0}" ));
! DATA(insert ( 2645 float8_accum float8_var_samp 0 1022 0 0 -1 f "{0,0,0}" ));
! DATA(insert ( 2646 numeric_accum numeric_var_samp 0 2281 128 0 -1 f _null_ ));
/* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148 int8_accum numeric_var_samp 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2149 int4_accum numeric_var_samp 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2150 int2_accum numeric_var_samp 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2151 float4_accum float8_var_samp 0 1022 0 0 -1 f "{0,0,0}" ));
! DATA(insert ( 2152 float8_accum float8_var_samp 0 1022 0 0 -1 f "{0,0,0}" ));
! DATA(insert ( 2153 numeric_accum numeric_var_samp 0 2281 128 0 -1 f _null_ ));
/* stddev_pop */
! DATA(insert ( 2724 int8_accum numeric_stddev_pop 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2725 int4_accum numeric_stddev_pop 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2726 int2_accum numeric_stddev_pop 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2727 float4_accum float8_stddev_pop 0 1022 0 0 -1 f "{0,0,0}" ));
! DATA(insert ( 2728 float8_accum float8_stddev_pop 0 1022 0 0 -1 f "{0,0,0}" ));
! DATA(insert ( 2729 numeric_accum numeric_stddev_pop 0 2281 128 0 -1 f _null_ ));
/* stddev_samp */
! DATA(insert ( 2712 int8_accum numeric_stddev_samp 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2713 int4_accum numeric_stddev_samp 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2714 int2_accum numeric_stddev_samp 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2715 float4_accum float8_stddev_samp 0 1022 0 0 -1 f "{0,0,0}" ));
! DATA(insert ( 2716 float8_accum float8_stddev_samp 0 1022 0 0 -1 f "{0,0,0}" ));
! DATA(insert ( 2717 numeric_accum numeric_stddev_samp 0 2281 128 0 -1 f _null_ ));
/* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154 int8_accum numeric_stddev_samp 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2155 int4_accum numeric_stddev_samp 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2156 int2_accum numeric_stddev_samp 0 2281 128 0 -1 f _null_ ));
! DATA(insert ( 2157 float4_accum float8_stddev_samp 0 1022 0 0 -1 f "{0,0,0}" ));
! DATA(insert ( 2158 float8_accum float8_stddev_samp 0 1022 0 0 -1 f "{0,0,0}" ));
! DATA(insert ( 2159 numeric_accum numeric_stddev_samp 0 2281 128 0 -1 f _null_ ));
/* SQL2003 binary regression aggregates */
! DATA(insert ( 2818 int8inc_float8_float8 - 0 20 0 0 -1 f "0" ));
! DATA(insert ( 2819 float8_regr_accum float8_regr_sxx 0 1022 0 0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2820 float8_regr_accum float8_regr_syy 0 1022 0 0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2821 float8_regr_accum float8_regr_sxy 0 1022 0 0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2822 float8_regr_accum float8_regr_avgx 0 1022 0 0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2823 float8_regr_accum float8_regr_avgy 0 1022 0 0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2824 float8_regr_accum float8_regr_r2 0 1022 0 0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2825 float8_regr_accum float8_regr_slope 0 1022 0 0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2826 float8_regr_accum float8_regr_intercept 0 1022 0 0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2827 float8_regr_accum float8_covar_pop 0 1022 0 0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2828 float8_regr_accum float8_covar_samp 0 1022 0 0 -1 f "{0,0,0,0,0,0}" ));
! DATA(insert ( 2829 float8_regr_accum float8_corr 0 1022 0 0 -1 f "{0,0,0,0,0,0}" ));
/* boolean-and and boolean-or */
! DATA(insert ( 2517 booland_statefunc - 58 16 0 0 -1 f _null_ ));
! DATA(insert ( 2518 boolor_statefunc - 59 16 0 0 -1 f _null_ ));
! DATA(insert ( 2519 booland_statefunc - 58 16 0 0 -1 f _null_ ));
/* bitwise integer */
! DATA(insert ( 2236 int2and - 0 21 0 0 -1 f _null_ ));
! DATA(insert ( 2237 int2or - 0 21 0 0 -1 f _null_ ));
! DATA(insert ( 2238 int4and - 0 23 0 0 -1 f _null_ ));
! DATA(insert ( 2239 int4or - 0 23 0 0 -1 f _null_ ));
! DATA(insert ( 2240 int8and - 0 20 0 0 -1 f _null_ ));
! DATA(insert ( 2241 int8or - 0 20 0 0 -1 f _null_ ));
! DATA(insert ( 2242 bitand - 0 1560 0 0 -1 f _null_ ));
! DATA(insert ( 2243 bitor - 0 1560 0 0 -1 f _null_ ));
/* xml */
! DATA(insert ( 2901 xmlconcat2 - 0 142 0 0 -1 f _null_ ));
/* array */
! DATA(insert ( 2335 array_agg_transfn array_agg_finalfn 0 2281 0 0 -1 f _null_ ));
/* text */
! DATA(insert ( 3538 string_agg_transfn string_agg_finalfn 0 2281 0 0 -1 f _null_ ));
/* bytea */
! DATA(insert ( 3545 bytea_string_agg_transfn bytea_string_agg_finalfn 0 2281 0 0 -1 f _null_ ));
/* json */
! DATA(insert ( 3175 json_agg_transfn json_agg_finalfn 0 2281 0 0 -1 f _null_ ));
!
! /* ordered set functions */
! DATA(insert ( 3931 - percentile_disc_final 0 0 0 0 1 t _null_));
! DATA(insert ( 3935 - percentile_cont_float8_final 0 0 0 0 1 t _null_));
! DATA(insert ( 3939 - percentile_cont_interval_final 0 0 0 0 1 t _null_));
! DATA(insert ( 3920 - rank_final 0 16 0 59 -2 t "f"));
! DATA(insert ( 3970 - dense_rank_final 0 16 0 59 -2 t "f"));
! DATA(insert ( 3972 - percent_rank_final 0 16 0 59 -2 t "f"));
! DATA(insert ( 3974 - cume_dist_final 0 16 0 58 -2 t "f"));
! DATA(insert ( 3976 - mode_final 0 0 0 0 0 t _null_));
! DATA(insert ( 3978 - percentile_disc_multi_final 0 0 0 0 1 t _null_));
! DATA(insert ( 3980 - percentile_cont_float8_multi_final 0 0 0 0 1 t _null_));
! DATA(insert ( 3982 - percentile_cont_interval_multi_final 0 0 0 0 1 t _null_));
/*
* prototypes for functions in pg_aggregate.c
***************
*** 244,249 **** DATA(insert ( 3175 json_agg_transfn json_agg_finalfn 0 2281 0 _null_ ));
--- 266,272 ----
extern Oid AggregateCreate(const char *aggName,
Oid aggNamespace,
int numArgs,
+ int numDirectArgs,
oidvector *parameterTypes,
Datum allParameterTypes,
Datum parameterModes,
***************
*** 252,259 **** extern Oid AggregateCreate(const char *aggName,
List *aggtransfnName,
List *aggfinalfnName,
List *aggsortopName,
Oid aggTransType,
int32 aggTransSpace,
! const char *agginitval);
#endif /* PG_AGGREGATE_H */
--- 275,286 ----
List *aggtransfnName,
List *aggfinalfnName,
List *aggsortopName,
+ List *aggtranssortopName,
Oid aggTransType,
int32 aggTransSpace,
! const char *agginitval,
! bool isStrict,
! bool isOrderedSetFunc,
! bool isHypotheticalSet);
#endif /* PG_AGGREGATE_H */
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 1973,1978 **** DATA(insert OID = 2232 ( pg_get_function_identity_arguments PGNSP PGUID 12 1
--- 1973,1982 ----
DESCR("identity argument list of a function");
DATA(insert OID = 2165 ( pg_get_function_result PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ pg_get_function_result _null_ _null_ _null_ ));
DESCR("result type of a function");
+ DATA(insert OID = 3179 ( pg_get_aggregate_arguments PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ pg_get_aggregate_arguments _null_ _null_ _null_ ));
+ DESCR("argument list of an aggregate function");
+ DATA(insert OID = 3180 ( pg_get_aggregate_identity_arguments PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ pg_get_aggregate_identity_arguments _null_ _null_ _null_ ));
+ DESCR("identity argument list of an aggregate function");
DATA(insert OID = 1686 ( pg_get_keywords PGNSP PGUID 12 10 400 0 0 f f f f t t s 0 0 2249 "" "{25,18,25}" "{o,o,o}" "{word,catcode,catdesc}" _null_ pg_get_keywords _null_ _null_ _null_ ));
DESCR("list of SQL keywords");
***************
*** 4758,4763 **** DESCR("SP-GiST support for quad tree over range");
--- 4762,4835 ----
/* event triggers */
DATA(insert OID = 3566 ( pg_event_trigger_dropped_objects PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25}" "{o,o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, schema_name, object_name, object_identity}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
DESCR("list objects dropped by the current command");
+
+ /* inverse distribution functions */
+ DATA(insert OID = 3931 ( percentile_disc PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 2283 "701 2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("discrete percentile");
+
+ DATA(insert OID = 3932 ( percentile_disc_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2283 "701 2283" _null_ _null_ _null_ _null_ percentile_disc_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+
+ DATA(insert OID = 3935 ( percentile_cont PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("continous distribution percentile for float8");
+
+ DATA(insert OID = 3936 ( percentile_cont_float8_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 701 "701 701" _null_ _null_ _null_ _null_ percentile_cont_float8_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+
+ DATA(insert OID = 3939 ( percentile_cont PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 1186 "701 1186" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("continous distribution percentile for interval");
+
+ DATA(insert OID = 3940 ( percentile_cont_interval_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1186 "701 1186" _null_ _null_ _null_ _null_ percentile_cont_interval_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+
+ /* hypothetical set functions */
+ DATA(insert OID = 3920 ( rank PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("hypothetical rank");
+
+ DATA(insert OID = 3969 ( rank_final PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_ hypothetical_rank_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+
+ DATA(insert OID = 3970 ( dense_rank PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("rank of hypothetical row without gaps");
+
+ DATA(insert OID = 3971 ( dense_rank_final PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_ hypothetical_dense_rank_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+
+ DATA(insert OID = 3972 ( percent_rank PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("fractional ranking of hypothetical row within a group");
+
+ DATA(insert OID = 3973 ( percent_rank_final PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_ hypothetical_percent_rank_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+
+ DATA(insert OID = 3974 ( cume_dist PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("cumulative distribution of hypothetical row in a group");
+
+ DATA(insert OID = 3975 ( cume_dist_final PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_ hypothetical_cume_dist_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+
+ DATA(insert OID = 3976 ( mode PGNSP PGUID 12 1 0 0 0 t f f f t f i 1 0 2283 2283 _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("most common value in group");
+ DATA(insert OID = 3977 ( mode_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2283 2283 _null_ _null_ _null_ _null_ mode_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+
+ DATA(insert OID = 3978 ( percentile_disc PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 2277 "1022 2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("multiple discrete percentiles");
+
+ DATA(insert OID = 3979 ( percentile_disc_multi_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2277 "1022 2283" _null_ _null_ _null_ _null_ percentile_disc_multi_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+
+ DATA(insert OID = 3980 ( percentile_cont PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 1022 "1022 701" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("multiple continuous percentiles of float8 values");
+
+ DATA(insert OID = 3981 ( percentile_cont_float8_multi_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1022 "1022 701" _null_ _null_ _null_ _null_ percentile_cont_float8_multi_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+
+ DATA(insert OID = 3982 ( percentile_cont PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 1187 "1022 1186" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("multiple continuous percentiles of interval values");
+
+ DATA(insert OID = 3983 ( percentile_cont_interval_multi_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1187 "1022 1186" _null_ _null_ _null_ _null_ percentile_cont_interval_multi_final _null_ _null_ _null_ ));
+ DESCR("ordered set final function");
+
/*
* Symbolic values for provolatile column: these indicate whether the result
* of a function is dependent *only* on the values of its explicit arguments,
*** a/src/include/fmgr.h
--- b/src/include/fmgr.h
***************
*** 651,656 **** extern void **find_rendezvous_variable(const char *varName);
--- 651,685 ----
extern int AggCheckCallContext(FunctionCallInfo fcinfo,
MemoryContext *aggcontext);
+ typedef struct Tuplesortstate fmTuplesortstate;
+ typedef struct tupleDesc *fmTupleDesc;
+ typedef struct TupleTableSlot fmTupleTableSlot;
+
+ extern int64 AggSetGetRowCount(FunctionCallInfo fcinfo);
+
+ extern void AggSetGetSortInfo(FunctionCallInfo fcinfo,
+ fmTuplesortstate **sortstate,
+ fmTupleDesc *tupdesc,
+ fmTupleTableSlot **tupslot,
+ Oid *datumtype);
+
+ /* int16 rather than AttrNumber here to avoid includes */
+ extern int AggSetGetDistinctInfo(FunctionCallInfo fcinfo,
+ fmTupleTableSlot **tupslot,
+ int16 **sortColIdx,
+ FmgrInfo **equalfns);
+
+ /* int16 rather than AttrNumber here to avoid includes */
+ extern int AggSetGetSortOperators(FunctionCallInfo fcinfo,
+ int16 **sortColIdx,
+ Oid **sortOperators,
+ Oid **sortEqOperators,
+ Oid **sortCollations,
+ bool **sortNullsFirst);
+
+ extern void AggSetGetPerTupleContext(FunctionCallInfo fcinfo,
+ MemoryContext *memcontext);
+
/*
* We allow plugin modules to hook function entry/exit. This is intended
* as support for loadable security policy modules, which may want to
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 588,593 **** typedef struct AggrefExprState
--- 588,594 ----
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ List *orddirectargs; /* Ordered direct arguments */
ExprState *aggfilter; /* FILTER expression */
int aggno; /* ID number for agg within its plan node */
} AggrefExprState;
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 426,432 **** typedef enum NodeTag
T_WindowObjectData, /* private in nodeWindowAgg.c */
T_TIDBitmap, /* in nodes/tidbitmap.h */
T_InlineCodeBlock, /* in nodes/parsenodes.h */
! T_FdwRoutine /* in foreign/fdwapi.h */
} NodeTag;
/*
--- 426,433 ----
T_WindowObjectData, /* private in nodeWindowAgg.c */
T_TIDBitmap, /* in nodes/tidbitmap.h */
T_InlineCodeBlock, /* in nodes/parsenodes.h */
! T_FdwRoutine, /* in foreign/fdwapi.h */
! T_AggStatePerAggData /* private in nodeAgg.c */
} NodeTag;
/*
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 303,308 **** typedef struct FuncCall
--- 303,309 ----
bool agg_star; /* argument was really '*' */
bool agg_distinct; /* arguments were labeled DISTINCT */
bool func_variadic; /* last argument was labeled VARIADIC */
+ bool has_within_group; /* WITHIN GROUP clause,if any */
struct WindowDef *over; /* OVER clause, if any */
int location; /* token location, or -1 if unknown */
} FuncCall;
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 247,255 **** typedef struct Aggref
--- 247,258 ----
List *args; /* arguments and sort expressions */
List *aggorder; /* ORDER BY (list of SortGroupClause) */
List *aggdistinct; /* DISTINCT (list of SortGroupClause) */
+ List *orddirectargs; /* Direct arguments for ordered set functions */
Expr *aggfilter; /* FILTER expression */
bool aggstar; /* TRUE if argument list was really '*' */
bool aggvariadic; /* TRUE if VARIADIC was used in call */
+ bool isordset; /* If node is from an ordered set function */
+ bool ishypothetical; /* If node is from a hypothetical set function */
Index agglevelsup; /* > 0 if agg belongs to outer query */
int location; /* token location, or -1 if unknown */
} Aggref;
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 412,417 **** PG_KEYWORD("where", WHERE, RESERVED_KEYWORD)
--- 412,418 ----
PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("window", WINDOW, RESERVED_KEYWORD)
PG_KEYWORD("with", WITH, RESERVED_KEYWORD)
+ PG_KEYWORD("within", WITHIN, UNRESERVED_KEYWORD)
PG_KEYWORD("without", WITHOUT, UNRESERVED_KEYWORD)
PG_KEYWORD("work", WORK, UNRESERVED_KEYWORD)
PG_KEYWORD("wrapper", WRAPPER, UNRESERVED_KEYWORD)
*** a/src/include/parser/parse_agg.h
--- b/src/include/parser/parse_agg.h
***************
*** 17,23 ****
extern void transformAggregateCall(ParseState *pstate, Aggref *agg,
List *args, List *aggorder,
! bool agg_distinct);
extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
WindowDef *windef);
--- 17,23 ----
extern void transformAggregateCall(ParseState *pstate, Aggref *agg,
List *args, List *aggorder,
! bool agg_distinct, bool agg_within_group);
extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
WindowDef *windef);
***************
*** 34,37 **** extern void build_aggregate_fnexprs(Oid *agg_input_types,
--- 34,51 ----
Expr **transfnexpr,
Expr **finalfnexpr);
+ void
+ build_orderedset_fnexprs(Oid *agg_input_types,
+ int agg_num_inputs,
+ bool agg_variadic,
+ Oid agg_result_type,
+ Oid agg_input_collation,
+ Oid *agg_input_collation_array,
+ Oid finalfn_oid,
+ Expr **finalfnexpr);
+
+ int get_aggregate_argtypes(Aggref *aggref,
+ Oid *inputTypes,
+ Oid *inputCollations);
+
#endif /* PARSE_AGG_H */
*** a/src/include/parser/parse_clause.h
--- b/src/include/parser/parse_clause.h
***************
*** 31,37 **** extern List *transformGroupClause(ParseState *pstate, List *grouplist,
ParseExprKind exprKind, bool useSQL99);
extern List *transformSortClause(ParseState *pstate, List *orderlist,
List **targetlist, ParseExprKind exprKind,
! bool resolveUnknown, bool useSQL99);
extern List *transformWindowDefinitions(ParseState *pstate,
List *windowdefs,
--- 31,37 ----
ParseExprKind exprKind, bool useSQL99);
extern List *transformSortClause(ParseState *pstate, List *orderlist,
List **targetlist, ParseExprKind exprKind,
! bool resolveUnknown, bool useSQL99, bool keepDuplicates);
extern List *transformWindowDefinitions(ParseState *pstate,
List *windowdefs,
*** a/src/include/parser/parse_func.h
--- b/src/include/parser/parse_func.h
***************
*** 38,51 **** typedef enum
FUNCDETAIL_NORMAL, /* found a matching regular function */
FUNCDETAIL_AGGREGATE, /* found a matching aggregate function */
FUNCDETAIL_WINDOWFUNC, /* found a matching window function */
! FUNCDETAIL_COERCION /* it's a type coercion request */
} FuncDetailCode;
-
extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
! List *agg_order, Expr *agg_filter,
! bool agg_star, bool agg_distinct, bool func_variadic,
! WindowDef *over, bool is_column, int location);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
--- 38,48 ----
FUNCDETAIL_NORMAL, /* found a matching regular function */
FUNCDETAIL_AGGREGATE, /* found a matching aggregate function */
FUNCDETAIL_WINDOWFUNC, /* found a matching window function */
! FUNCDETAIL_COERCION, /* it's a type coercion request */
} FuncDetailCode;
extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
! int location, FuncCall *fn);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
***************
*** 66,73 **** extern FuncCandidateList func_select_candidate(int nargs,
extern void make_fn_arguments(ParseState *pstate,
List *fargs,
Oid *actual_arg_types,
! Oid *declared_arg_types);
extern const char *funcname_signature_string(const char *funcname, int nargs,
List *argnames, const Oid *argtypes);
--- 63,72 ----
extern void make_fn_arguments(ParseState *pstate,
List *fargs,
+ List *agg_order,
Oid *actual_arg_types,
! Oid *declared_arg_types,
! bool requiresUnification);
extern const char *funcname_signature_string(const char *funcname, int nargs,
List *argnames, const Oid *argtypes);
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 665,670 **** extern Datum pg_get_functiondef(PG_FUNCTION_ARGS);
--- 665,672 ----
extern Datum pg_get_function_arguments(PG_FUNCTION_ARGS);
extern Datum pg_get_function_identity_arguments(PG_FUNCTION_ARGS);
extern Datum pg_get_function_result(PG_FUNCTION_ARGS);
+ extern Datum pg_get_aggregate_arguments(PG_FUNCTION_ARGS);
+ extern Datum pg_get_aggregate_identity_arguments(PG_FUNCTION_ARGS);
extern char *deparse_expression(Node *expr, List *dpcontext,
bool forceprefix, bool showimplicit);
extern List *deparse_context_for(const char *aliasname, Oid relid);
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
***************
*** 1310,1315 **** select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
--- 1310,1529 ----
{"(2,2,bar)","(3,1,baz)"}
(1 row)
+ -- ordered set functions
+ select p, percentile_cont(p) within group (order by x::float8) from generate_series(1,5) x,
+ (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p)
+ group by p order by p;
+ p | percentile_cont
+ ------+-----------------
+ 0 | 1
+ 0.1 | 1.4
+ 0.25 | 2
+ 0.4 | 2.6
+ 0.5 | 3
+ 0.6 | 3.4
+ 0.75 | 4
+ 0.9 | 4.6
+ 1 | 5
+ (9 rows)
+
+ select p, percentile_cont(p order by p) within group (order by x::float8)
+ from generate_series(1,5) x, (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p)
+ group by p order by x;
+ ERROR: cannot have multiple ORDER BY clauses with WITHIN GROUP
+ LINE 1: select p, percentile_cont(p order by p) within group (order ...
+ ^
+ select p, sum() within group (order by x::float8)
+ from generate_series(1,5) x,
+ (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p;
+ ERROR: sum(double precision) is not an ordered set function
+ select p, percentile_cont(p,p) from generate_series(1,5) x,
+ (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p;
+ ERROR: WITHIN GROUP is required for call to ordered set function percentile_cont
+ LINE 1: select p, percentile_cont(p,p) from generate_series(1,5) x,
+ ^
+ select percentile_cont(0.5) within group (order by b) from aggtest;
+ percentile_cont
+ ------------------
+ 53.4485001564026
+ (1 row)
+
+ select percentile_cont(0.5) within group (order by b),sum(b) from aggtest;
+ percentile_cont | sum
+ ------------------+---------
+ 53.4485001564026 | 431.773
+ (1 row)
+
+ select percentile_cont(0.5) within group (order by thousand) from tenk1;
+ percentile_cont
+ -----------------
+ 499.5
+ (1 row)
+
+ select percentile_disc(0.5) within group (order by thousand) from tenk1;
+ percentile_disc
+ -----------------
+ 499
+ (1 row)
+
+ select rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+ rank
+ ------
+ 5
+ (1 row)
+
+ select cume_dist(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+ cume_dist
+ -----------
+ 0.875
+ (1 row)
+
+ select percent_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4),(5)) v(x);
+ percent_rank
+ --------------
+ 0.5
+ (1 row)
+
+ select dense_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+ dense_rank
+ ------------
+ 3
+ (1 row)
+
+ select percentile_disc(array[0,0.1,0.25,0.5,0.75,0.9,1]) within group (order by thousand) from tenk1;
+ percentile_disc
+ ----------------------------
+ {0,99,249,499,749,899,999}
+ (1 row)
+
+ select percentile_cont(array[0,0.25,0.5,0.75,1]) within group (order by thousand) from tenk1;
+ percentile_cont
+ -----------------------------
+ {0,249.75,499.5,749.25,999}
+ (1 row)
+
+ select percentile_disc(array[[null,1,0.5],[0.75,0.25,null]]) within group (order by thousand) from tenk1;
+ percentile_disc
+ ---------------------------------
+ {{NULL,999,499},{749,249,NULL}}
+ (1 row)
+
+ select percentile_cont(array[0,1,0.25,0.75,0.5,1]) within group (order by x)
+ from generate_series(1,6) x;
+ percentile_cont
+ -----------------------
+ {1,6,2.25,4.75,3.5,5}
+ (1 row)
+
+ select ten, mode() within group (order by string4) from tenk1 group by ten;
+ ten | mode
+ -----+--------
+ 0 | HHHHxx
+ 1 | OOOOxx
+ 2 | VVVVxx
+ 3 | OOOOxx
+ 4 | HHHHxx
+ 5 | HHHHxx
+ 6 | OOOOxx
+ 7 | AAAAxx
+ 8 | VVVVxx
+ 9 | VVVVxx
+ (10 rows)
+
+ select percentile_disc(array[0.25,0.5,0.75]) within group (order by x)
+ from unnest('{fred,jim,fred,jack,jill,fred,jill,jim,jim,sheila,jim,sheila}'::text[]) u(x);
+ percentile_disc
+ -----------------
+ {fred,jill,jim}
+ (1 row)
+
+ -- ordered set funcs can't use ungrouped direct args:
+ select rank(x) within group (order by x) from generate_series(1,5) x;
+ ERROR: column "x.x" must appear in the GROUP BY clause or be used in an aggregate function
+ LINE 1: select rank(x) within group (order by x) from generate_serie...
+ ^
+ -- collation:
+ select pg_collation_for(percentile_disc(1) within group (order by x collate "POSIX"))
+ from (values ('fred'),('jim')) v(x);
+ pg_collation_for
+ ------------------
+ "POSIX"
+ (1 row)
+
+ -- hypothetical type unification and argument failures:
+ select rank(3) within group (order by x) from (values ('fred'),('jim')) v(x);
+ ERROR: WITHIN GROUP types text and integer cannot be matched
+ LINE 1: select rank(3) within group (order by x) from (values ('fred...
+ ^
+ select rank(3) within group (order by stringu1,stringu2) from tenk1;
+ ERROR: function rank has 2 ordering columns but 1 hypothetical arguments
+ LINE 1: select rank(3) within group (order by stringu1,stringu2) fro...
+ ^
+ select rank('fred') within group (order by x) from generate_series(1,5) x;
+ ERROR: invalid input syntax for integer: "fred"
+ LINE 1: select rank('fred') within group (order by x) from generate_...
+ ^
+ select rank('adam'::text collate "C") within group (order by x collate "POSIX")
+ from (values ('fred'),('jim')) v(x);
+ ERROR: collation mismatch between explicit collations "C" and "POSIX"
+ LINE 1: ...adam'::text collate "C") within group (order by x collate "P...
+ ^
+ -- hypothetical type unification successes:
+ select rank('adam'::varchar) within group (order by x) from (values ('fred'),('jim')) v(x);
+ rank
+ ------
+ 1
+ (1 row)
+
+ select rank('3') within group (order by x) from generate_series(1,5) x;
+ rank
+ ------
+ 3
+ (1 row)
+
+ -- divide by zero check
+ select percent_rank(0) within group (order by x) from generate_series(1,0) x;
+ percent_rank
+ --------------
+ 0
+ (1 row)
+
+ -- deparse and multiple features:
+ create view aggordview1 as
+ select ten,
+ percentile_disc(0.5) within group (order by thousand) as p50,
+ percentile_disc(0.5) within group (order by thousand) filter (where hundred=1) as px,
+ rank(5,'AZZZZ',50) within group (order by hundred, string4 desc, hundred)
+ from tenk1
+ group by ten order by ten;
+ select pg_get_viewdef('aggordview1');
+ pg_get_viewdef
+ -------------------------------------------------------------------------------------------------------------------------------
+ SELECT tenk1.ten, +
+ percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50, +
+ percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
+ rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank +
+ FROM tenk1 +
+ GROUP BY tenk1.ten +
+ ORDER BY tenk1.ten;
+ (1 row)
+
+ select * from aggordview1 order by ten;
+ ten | p50 | px | rank
+ -----+-----+-----+------
+ 0 | 490 | | 101
+ 1 | 491 | 401 | 101
+ 2 | 492 | | 101
+ 3 | 493 | | 101
+ 4 | 494 | | 101
+ 5 | 495 | | 67
+ 6 | 496 | | 1
+ 7 | 497 | | 1
+ 8 | 498 | | 1
+ 9 | 499 | | 1
+ (10 rows)
+
+ drop view aggordview1;
-- variadic aggregates
select least_agg(q1,q2) from int8_tbl;
least_agg
*** a/src/test/regress/expected/opr_sanity.out
--- b/src/test/regress/expected/opr_sanity.out
***************
*** 700,708 **** SELECT * FROM funcdescs
-- **************** pg_aggregate ****************
-- Look for illegal values in pg_aggregate fields.
SELECT ctid, aggfnoid::oid
FROM pg_aggregate as p1
! WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0 OR aggtransspace < 0;
ctid | aggfnoid
------+----------
(0 rows)
--- 700,717 ----
-- **************** pg_aggregate ****************
-- Look for illegal values in pg_aggregate fields.
+ -- ordered set functions can't have transfns, and must
+ -- have finalfns, but may or may not have transtypes.
+ -- other aggs must have transfns and transtypes with
+ -- optional finalfns.
SELECT ctid, aggfnoid::oid
FROM pg_aggregate as p1
! WHERE aggfnoid = 0
! OR CASE WHEN aggisordsetfunc
! THEN aggtransfn <> 0 OR aggfinalfn = 0
! ELSE aggtransfn = 0 OR aggtranstype = 0
! END
! OR aggtransspace < 0;
ctid | aggfnoid
------+----------
(0 rows)
***************
*** 764,771 **** WHERE a.aggfnoid = p.oid AND
a.aggfinalfn = pfn.oid AND
(pfn.proretset
OR NOT binary_coercible(pfn.prorettype, p.prorettype)
! OR pfn.pronargs != 1
! OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]));
aggfnoid | proname | oid | proname
----------+---------+-----+---------
(0 rows)
--- 773,781 ----
a.aggfinalfn = pfn.oid AND
(pfn.proretset
OR NOT binary_coercible(pfn.prorettype, p.prorettype)
! OR (aggisordsetfunc IS FALSE
! AND (pfn.pronargs != 1
! OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]))));
aggfnoid | proname | oid | proname
----------+---------+-----+---------
(0 rows)
***************
*** 857,866 **** ORDER BY 1;
count("any") | count()
(1 row)
! -- For the same reason, we avoid creating built-in variadic aggregates.
! SELECT oid, proname
! FROM pg_proc AS p
! WHERE proisagg AND provariadic != 0;
oid | proname
-----+---------
(0 rows)
--- 867,878 ----
count("any") | count()
(1 row)
! -- For the same reason, we avoid creating built-in variadic aggregates, except
! -- ordered set functions (which have their own syntax and are not subject to
! -- the misplaced ORDER BY issue).
! SELECT p.oid, proname
! FROM pg_proc AS p JOIN pg_aggregate AS a ON (a.aggfnoid=p.oid)
! WHERE proisagg AND provariadic != 0 AND NOT a.aggisordsetfunc;
oid | proname
-----+---------
(0 rows)
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
***************
*** 493,498 **** select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
--- 493,564 ----
from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
generate_series(1,2) i;
+ -- ordered set functions
+
+ select p, percentile_cont(p) within group (order by x::float8) from generate_series(1,5) x,
+ (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p)
+ group by p order by p;
+ select p, percentile_cont(p order by p) within group (order by x::float8)
+ from generate_series(1,5) x, (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p)
+ group by p order by x;
+ select p, sum() within group (order by x::float8)
+ from generate_series(1,5) x,
+ (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p;
+ select p, percentile_cont(p,p) from generate_series(1,5) x,
+ (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p;
+ select percentile_cont(0.5) within group (order by b) from aggtest;
+ select percentile_cont(0.5) within group (order by b),sum(b) from aggtest;
+ select percentile_cont(0.5) within group (order by thousand) from tenk1;
+ select percentile_disc(0.5) within group (order by thousand) from tenk1;
+ select rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+ select cume_dist(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+ select percent_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4),(5)) v(x);
+ select dense_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+
+ select percentile_disc(array[0,0.1,0.25,0.5,0.75,0.9,1]) within group (order by thousand) from tenk1;
+ select percentile_cont(array[0,0.25,0.5,0.75,1]) within group (order by thousand) from tenk1;
+ select percentile_disc(array[[null,1,0.5],[0.75,0.25,null]]) within group (order by thousand) from tenk1;
+ select percentile_cont(array[0,1,0.25,0.75,0.5,1]) within group (order by x)
+ from generate_series(1,6) x;
+
+ select ten, mode() within group (order by string4) from tenk1 group by ten;
+
+ select percentile_disc(array[0.25,0.5,0.75]) within group (order by x)
+ from unnest('{fred,jim,fred,jack,jill,fred,jill,jim,jim,sheila,jim,sheila}'::text[]) u(x);
+
+ -- ordered set funcs can't use ungrouped direct args:
+ select rank(x) within group (order by x) from generate_series(1,5) x;
+
+ -- collation:
+ select pg_collation_for(percentile_disc(1) within group (order by x collate "POSIX"))
+ from (values ('fred'),('jim')) v(x);
+
+ -- hypothetical type unification and argument failures:
+ select rank(3) within group (order by x) from (values ('fred'),('jim')) v(x);
+ select rank(3) within group (order by stringu1,stringu2) from tenk1;
+ select rank('fred') within group (order by x) from generate_series(1,5) x;
+ select rank('adam'::text collate "C") within group (order by x collate "POSIX")
+ from (values ('fred'),('jim')) v(x);
+ -- hypothetical type unification successes:
+ select rank('adam'::varchar) within group (order by x) from (values ('fred'),('jim')) v(x);
+ select rank('3') within group (order by x) from generate_series(1,5) x;
+
+ -- divide by zero check
+ select percent_rank(0) within group (order by x) from generate_series(1,0) x;
+
+ -- deparse and multiple features:
+ create view aggordview1 as
+ select ten,
+ percentile_disc(0.5) within group (order by thousand) as p50,
+ percentile_disc(0.5) within group (order by thousand) filter (where hundred=1) as px,
+ rank(5,'AZZZZ',50) within group (order by hundred, string4 desc, hundred)
+ from tenk1
+ group by ten order by ten;
+
+ select pg_get_viewdef('aggordview1');
+ select * from aggordview1 order by ten;
+ drop view aggordview1;
+
-- variadic aggregates
select least_agg(q1,q2) from int8_tbl;
select least_agg(variadic array[q1,q2]) from int8_tbl;
*** a/src/test/regress/sql/opr_sanity.sql
--- b/src/test/regress/sql/opr_sanity.sql
***************
*** 564,573 **** SELECT * FROM funcdescs
-- **************** pg_aggregate ****************
-- Look for illegal values in pg_aggregate fields.
SELECT ctid, aggfnoid::oid
FROM pg_aggregate as p1
! WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0 OR aggtransspace < 0;
-- Make sure the matching pg_proc entry is sensible, too.
--- 564,582 ----
-- **************** pg_aggregate ****************
-- Look for illegal values in pg_aggregate fields.
+ -- ordered set functions can't have transfns, and must
+ -- have finalfns, but may or may not have transtypes.
+ -- other aggs must have transfns and transtypes with
+ -- optional finalfns.
SELECT ctid, aggfnoid::oid
FROM pg_aggregate as p1
! WHERE aggfnoid = 0
! OR CASE WHEN aggisordsetfunc
! THEN aggtransfn <> 0 OR aggfinalfn = 0
! ELSE aggtransfn = 0 OR aggtranstype = 0
! END
! OR aggtransspace < 0;
-- Make sure the matching pg_proc entry is sensible, too.
***************
*** 618,625 **** WHERE a.aggfnoid = p.oid AND
a.aggfinalfn = pfn.oid AND
(pfn.proretset
OR NOT binary_coercible(pfn.prorettype, p.prorettype)
! OR pfn.pronargs != 1
! OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]));
-- If transfn is strict then either initval should be non-NULL, or
-- input type should match transtype so that the first non-null input
--- 627,635 ----
a.aggfinalfn = pfn.oid AND
(pfn.proretset
OR NOT binary_coercible(pfn.prorettype, p.prorettype)
! OR (aggisordsetfunc IS FALSE
! AND (pfn.pronargs != 1
! OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]))));
-- If transfn is strict then either initval should be non-NULL, or
-- input type should match transtype so that the first non-null input
***************
*** 685,695 **** WHERE p1.oid < p2.oid AND p1.proname = p2.proname AND
array_dims(p1.proargtypes) != array_dims(p2.proargtypes)
ORDER BY 1;
! -- For the same reason, we avoid creating built-in variadic aggregates.
! SELECT oid, proname
! FROM pg_proc AS p
! WHERE proisagg AND provariadic != 0;
-- For the same reason, built-in aggregates with default arguments are no good.
--- 695,707 ----
array_dims(p1.proargtypes) != array_dims(p2.proargtypes)
ORDER BY 1;
! -- For the same reason, we avoid creating built-in variadic aggregates, except
! -- ordered set functions (which have their own syntax and are not subject to
! -- the misplaced ORDER BY issue).
! SELECT p.oid, proname
! FROM pg_proc AS p JOIN pg_aggregate AS a ON (a.aggfnoid=p.oid)
! WHERE proisagg AND provariadic != 0 AND NOT a.aggisordsetfunc;
-- For the same reason, built-in aggregates with default arguments are no good.
On 11/21/2013 11:04 AM, Atri Sharma wrote:
Please find attached the latest patch for WITHIN GROUP. This patch is
after fixing the merge conflicts.
I have spent quite some time on this and the previous versions. Here is
my review, following the questions on the wiki.
This patch is in context diff format and applies cleanly to today's
master. It contains several regression tests and for the most part,
good documentation. I would like to see at least one example of using
each of the two types of function (hypothetical and inverted
distribution) in section 4.2.8.
This patch implements what it says it does. We don't already have a way
to get these results without this patch that I know of, and I think we
do want it. I certainly want it. I do not have a copy of the SQL
standard, but I have full faith in the Andrew Gierth's claim that this
is part of it. Even if not, implementation details were brought up
during design and agreed upon by this list[1]/messages/by-id/2b8b55b8ba82f83ef4e6070b95fb0cd0@news-out.riddles.org.uk. I don't see how anything
here could be dangerous. The custom ordered set functions I made
correctly passed a round-trip through dump/restore.
The code compiles without warning. All of the clean tests I did worked
as expected, and all of the dirty tests failed elegantly.
I did not find any corner cases, and I looked in as many corners as I
could think of. I didn't manage to trigger any assertion failures and I
didn't crash it.
I found no noticeable issues with performance, either directly or as
side effects.
I am not the most competent with code review so I'll be relying on
further review by another reviewer or the final committer. The patch
fixed the project comments/messages style issues I raised in my previous
review. I found the code comments lacking in some places (like
inversedistribution.c:mode_final for example) but I can't say if the
really is too terse, or if it's just me. On the other hand, I thought
the explanation blocks in the code comments were adequately descriptive.
There is some room for improvement in future versions. The query select
mode() within group (order by x) over (partition by y) from ... should
be valid but isn't. This was explained by Andrew on IRC as being
non-trivial: "specifically, we implemented WITHIN GROUP by repurposing
the infrastructure already present for agg(distinct ...) and agg(x order
by y) which are also not yet supported for
aggregate-as-window-function". I assume then that evolution on one side
will benefit the other.
All in all, I believe this is ready for committer.
[1]: /messages/by-id/2b8b55b8ba82f83ef4e6070b95fb0cd0@news-out.riddles.org.uk
/messages/by-id/2b8b55b8ba82f83ef4e6070b95fb0cd0@news-out.riddles.org.uk
--
Vik
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
"Vik" == Vik Fearing <vik.fearing@dalibo.com> writes:
Vik> I certainly want it. I do not have a copy of the SQL standard,
Vik> but I have full faith in the Andrew Gierth's claim that this is
Vik> part of it.
For reference, this is how I believe it matches up against the spec
(I'm working from the 2008 final):
10.9 <aggregate function>:
<hypothetical set function> is intended to be implemented in this
patch exactly as per spec.
<inverse distribution function>: the spec defines two of these,
PERCENTILE_CONT and PERCENTILE_DISC:
PERCENTILE_CONT is defined in the spec for numeric types, in which
case it returns an approximate numeric result, and for interval, in
which case it returns interval. Our patch defines percentile_cont
functions for float8 and interval input types, relying on implicit
casting to float8 to handle other numeric input types.
As an extension to the spec, we define a percentile_cont function
that returns an array of percentile values in one call, as suggested
by some users.
PERCENTILE_DISC is defined in the spec for the same types as _CONT.
Our version on the other hand accepts any type which can be sorted,
and returns the same type. This does mean that our version may return
an exact numeric type rather than an approximate one, so this is a
possible slight deviation from the spec.
i.e. our percentile_disc(float8) within group (order by bigint)
returns a bigint, while the spec seems to imply it should return an
approximate numeric type (i.e. float*).
Again, we additionally provide an array version which is not in the
spec.
mode() is not in the spec, we just threw it in because it was easy.
6.10 <window function>
The spec says that <hypothetical set function> is not allowed as a
window function.
The spec does not forbid other <ordered set function>s in a window
function call, but we have NOT attempted to implement this (largely
for the same reasons that DISTINCT and ORDER BY are not implemented
for aggregates as window functions).
Conformance: all the relevant features are parts of T612, "Advanced
OLAP Operations", which we already list in the docs on the unsupported
list with the comment "some forms supported". Maybe that could be
changed now to "most forms supported", but that's a subjective call
(and one we didn't really consider doing in this patch).
--
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
Atri Sharma <atri.jiit@gmail.com> writes:
Please find attached the latest patch for WITHIN GROUP. This patch is
after fixing the merge conflicts.
I've started to look at this patch now. I have a couple of immediate
reactions to the catalog changes:
1. I really hate the way you've overloaded the transvalue to do something
that has approximately nothing to do with transition state (and haven't
updated catalogs.sgml to explain that, either). Seems like it'd be
cleaner to just hardwire a bool column that distinguishes regular and
hypothetical input rows. And why do you need aggtranssortop for that?
I fail to see the point of sorting on the flag column.
2 I also don't care for the definition of aggordnargs, which is the number
of direct arguments to an ordered set function, except when it isn't.
Rather than overloading it to be both a count and a couple of flags,
I wonder whether we shouldn't expand aggisordsetfunc to be a three-way
"aggregate kind" field (ordinary agg, ordered set, or hypothetical set
function).
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
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> 1. I really hate the way you've overloaded the transvalue to do
Tom> something that has approximately nothing to do with transition
Tom> state (and haven't updated catalogs.sgml to explain that,
Tom> either). Seems like it'd be cleaner to just hardwire a bool
Tom> column that distinguishes regular and hypothetical input rows.
The intention here is that while the provided functions all fit the
spec's idea of how inverse distribution or hypothetical set functions
work, the actual implementation mechanisms are more generally
applicable than that and a user-supplied function could well find
something else useful to do with them. Accordingly, hardcoding stuff
seemed inappropriate.
Tom> And why do you need aggtranssortop for that? I fail to see the
Tom> point of sorting on the flag column.
It is convenient to the implementation to be able to rely on
encountering the hypothetical row deterministically before (or in some
cases after, as in cume_dist) its peers in the remainder of the sort
order. Removing that sort would make the results of the functions
incorrect.
There should probably be some comments about that. oops.
Tom> 2 I also don't care for the definition of aggordnargs, which is
Tom> the number of direct arguments to an ordered set function,
Tom> except when it isn't. Rather than overloading it to be both a
Tom> count and a couple of flags, I wonder whether we shouldn't
Tom> expand aggisordsetfunc to be a three-way "aggregate kind" field
Tom> (ordinary agg, ordered set, or hypothetical set function).
It would still be overloaded in some sense because a non-hypothetical
ordered set function could still take an arbitrary number of args
(using variadic "any") - there aren't any provided, but there's no
good reason to disallow user-defined functions doing that - so you'd
still need a special value like -1 for aggordnargs to handle that.
--
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
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> 1. I really hate the way you've overloaded the transvalue to do
Tom> something that has approximately nothing to do with transition
Tom> state (and haven't updated catalogs.sgml to explain that,
Tom> either). Seems like it'd be cleaner to just hardwire a bool
Tom> column that distinguishes regular and hypothetical input rows.
The intention here is that while the provided functions all fit the
spec's idea of how inverse distribution or hypothetical set functions
work, the actual implementation mechanisms are more generally
applicable than that and a user-supplied function could well find
something else useful to do with them. Accordingly, hardcoding stuff
seemed inappropriate.
Tom> And why do you need aggtranssortop for that? I fail to see the
Tom> point of sorting on the flag column.
It is convenient to the implementation to be able to rely on
encountering the hypothetical row deterministically before (or in some
cases after, as in cume_dist) its peers in the remainder of the sort
order. Removing that sort would make the results of the functions
incorrect.
Well, okay, but you've not said anything that wouldn't be handled just as
well by some logic that adds a fixed integer-constant-zero flag column to
the rows going into the tuplesort. The function-specific code could then
inject hypothetical row(s) with other values, eg 1 or -1, to get the
results you describe. If there's any flexibility improvement from
allowing a different constant value than zero, or a different datatype
than integer, I don't see what it'd be.
On the other side of the coin, repurposing the transition-value catalog
infrastructure to do this totally different thing is confusing. And what
if someday somebody has a use for a regular transition value along with
this stuff? What you've done is unorthogonal for no very good reason.
(Actually, I'm wondering whether it wouldn't be better if the tuplesort
object *were* the transition value, and were managed primarily by the
aggregate function's code; in particular expecting the agg's transition
function to insert rows into the tuplesort object. We could provide
helper functions to largely negate any duplication-of-code objections,
I'd think. In this view the WITHIN GROUP columns aren't so different from
regular aggregate arguments, but there'd need to be some way for the
aggregate function to get hold of the sorting-semantics decoration on
them.)
Tom> 2 I also don't care for the definition of aggordnargs, which is
Tom> the number of direct arguments to an ordered set function,
Tom> except when it isn't. Rather than overloading it to be both a
Tom> count and a couple of flags, I wonder whether we shouldn't
Tom> expand aggisordsetfunc to be a three-way "aggregate kind" field
Tom> (ordinary agg, ordered set, or hypothetical set function).
It would still be overloaded in some sense because a non-hypothetical
ordered set function could still take an arbitrary number of args
(using variadic "any") - there aren't any provided, but there's no
good reason to disallow user-defined functions doing that - so you'd
still need a special value like -1 for aggordnargs to handle that.
Sure. But a -1 to indicate "not applicable" doesn't seem like it's
too much of a stretch. It's the -2 business that's bothering me.
Again, that seems unnecessarily non-orthogonal --- who's to say which
functions would want to constrain the number of direct arguments and
which wouldn't? (I wonder whether having this info in the catalogs
isn't the wrong thing anyhow, as opposed to expecting the functions
themselves to check the argument count at runtime.)
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
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> Well, okay, but you've not said anything that wouldn't be
Tom> handled just as well by some logic that adds a fixed
Tom> integer-constant-zero flag column to the rows going into the
Tom> tuplesort.
Adding such a column unconditionally even for non-hypothetical
functions would break the optimization for sorting a single column
(which is a big deal, something like 3x speed difference, for by-value
types).
Adding it only for hypothetical set functions is making a distinction
in how functions are executed that I don't think is warranted -
imagine for example a function that calculates some measure over a
frequency distribution by adding a known set of boundary values to the
sort; this would not be a hypothetical set function in terms of
argument processing, but it would still benefit from the extra sort
column. I did not want to unnecessarily restrict such possibilities.
It would still be overloaded in some sense because a non-hypothetical
ordered set function could still take an arbitrary number of args
(using variadic "any") - there aren't any provided, but there's no
good reason to disallow user-defined functions doing that - so you'd
still need a special value like -1 for aggordnargs to handle that.
Tom> Sure. But a -1 to indicate "not applicable" doesn't seem like it's
Tom> too much of a stretch. It's the -2 business that's bothering me.
Tom> Again, that seems unnecessarily non-orthogonal --- who's to say which
Tom> functions would want to constrain the number of direct arguments and
Tom> which wouldn't? (I wonder whether having this info in the catalogs
Tom> isn't the wrong thing anyhow, as opposed to expecting the functions
Tom> themselves to check the argument count at runtime.)
Not checking the number of arguments to a function until runtime seems
a bit on the perverse side. Having a fixed number of direct args is
the "normal" case (as seen from the fact that all the non-hypothetical
ordered set functions in the spec and in our patch have fixed argument
counts).
--
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
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> Well, okay, but you've not said anything that wouldn't be
Tom> handled just as well by some logic that adds a fixed
Tom> integer-constant-zero flag column to the rows going into the
Tom> tuplesort.
Adding such a column unconditionally even for non-hypothetical
functions would break the optimization for sorting a single column
(which is a big deal, something like 3x speed difference, for by-value
types).
Well, sure, but I was only suggesting adding it when the aggregate asks
for it, probably via a new flag column in pg_aggregate. The question
you're evading is what additional functionality could be had if the
aggregate could demand a different datatype or constant value for the
flag column.
Adding it only for hypothetical set functions is making a distinction
in how functions are executed that I don't think is warranted -
That seems like rather a curious argument from someone who's willing to
give up the ability to specify a regular transition value concurrently
with the flag column.
But anyway, what I'm thinking right now is that these questions would all
go away if the aggregate transfunction were receiving the rows and
sticking them into the tuplestore. It could add whatever columns it felt
like.
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
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> Well, sure, but I was only suggesting adding it when the
Tom> aggregate asks for it, probably via a new flag column in
Tom> pg_aggregate.
Sure, I was only pointing out the necessity.
Tom> The question you're evading is what additional functionality
Tom> could be had if the aggregate could demand a different datatype
Tom> or constant value for the flag column.
I don't really see a question there to answer - I simply chose to
provide a general mechanism rather than make assumptions about what
future users of the code would desire. I have no specific application
in mind that would require some other type.
Adding it only for hypothetical set functions is making a
distinction in how functions are executed that I don't think is
warranted -
Tom> That seems like rather a curious argument from someone who's
Tom> willing to give up the ability to specify a regular transition
Tom> value concurrently with the flag column.
In the current patch the idea of also specifying a regular transition
value is meaningless since there is no transition function.
Tom> But anyway, what I'm thinking right now is that these questions
Tom> would all go away if the aggregate transfunction were receiving
Tom> the rows and sticking them into the tuplestore. It could add
Tom> whatever columns it felt like.
True, but this ends up duplicating the sorting functionality of
nodeAgg that we are leveraging off in the first place. I think this
will be somewhat more intrusive and likely slower.
--
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
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> But anyway, what I'm thinking right now is that these questions
Tom> would all go away if the aggregate transfunction were receiving
Tom> the rows and sticking them into the tuplestore. It could add
Tom> whatever columns it felt like.
True, but this ends up duplicating the sorting functionality of
nodeAgg that we are leveraging off in the first place. I think this
will be somewhat more intrusive and likely slower.
Hm, it's just a refactoring of the same code we'd have to have anyway,
so I'm not seeing a reason to assume it'd be slower. If anything,
this approach would open more opportunities for function-specific
optimizations, which in the long run could be faster. (I'm not
claiming that any such optimizations would be in the first version.)
In hindsight I wonder if it wasn't a mistake to embed ordered-aggregate
support in nodeAgg.c the way we did. We could have dumped that
responsibility into some sort of wrapper around specific aggregates,
with an option for some aggregates to skip the wrapper and handle it
themselves. A trivial, and perhaps not very useful, example is that
non-order-sensitive aggregates like MIN/MAX/COUNT could have been coded
to simply ignore any ordering request. I can't immediately think of any
examples that are compelling enough to justify such a refactoring now ---
unless it turns out to make WITHIN GROUP easier.
Anyway, I'm going to go off and look at the WITHIN GROUP patch with these
ideas in mind.
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
Further questions about WITHIN GROUP:
I believe that the spec requires that the "direct" arguments of an inverse
or hypothetical-set aggregate must not contain any Vars of the current
query level. They don't manage to say that in plain English, of course,
but in the <hypothetical set function> case the behavior is defined in
terms of this sub-select:
( SELECT 0, SK1, ..., SKK
FROM TNAME UNION ALL
VALUES (1, VE1, ..., VEK) )
where SKn are the sort key values, TNAME is an alias for the input table,
and VEn are the direct arguments. Any input-table Vars the aggregate
might refer to are thus in scope only for the SKn not the VEn. (This
definition also makes it clear that the VEn are to be evaluated once,
not once per row.) In the <inverse distribution function> case, they
resort to claiming that the value of the <inverse distribution function
argument> can be replaced by a numeric literal, which again makes it clear
that it's supposed to be evaluated just once.
So that's all fine, but the patch seems a bit confused about it. If you
try to cheat, you get an error message that's less than apropos:
regression=# select cume_dist(f1) within group(order by f1) from text_tbl ;
ERROR: column "text_tbl.f1" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: select cume_dist(f1) within group(order by f1) from text_tbl...
^
I'd have hoped for a message along the line of "fixed arguments of
cume_dist() must not contain variables", similar to the phrasing we
use when you try to put a variable in LIMIT.
Also, there are some comments and code changes in nodeAgg.c that seem
to be on the wrong page here:
+ /*
+ * 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;
+
The last two lines of that comment are new, all the rest was moved from
someplace else. Those last two lines are wrong according to the above
reasoning, and if they were correct the argument made in the pre-existing
part of the comment would be all wet, meaning the code could in fact crash
when trying to evaluate a Var reference in finalize_aggregates.
So I'm inclined to undo this part of the patch (and anything else that's
rearranging logic with an eye to Var references in finalize_aggregates),
and to try to fix parse_agg.c so it gives a more specific error message
for this case.
After looking at this I'm a bit less enthused about the notion of hybrid
aggregates than I was before. I now see that the spec intends that when
the WITHIN GROUP notation is used, *all* the arguments to the left of
WITHIN GROUP are meant to be fixed evaluate-once arguments. While we
could possibly define aggregates for which some of those argument
positions are treated as evaluate-once-per-row arguments, I'm realizing
that that'd likely be pretty darn confusing for users.
What I now think we should do about the added pg_aggregate columns is
to have:
aggnfixedargs int number of fixed arguments, excluding any
hypothetical ones (always 0 for normal aggregates)
aggkind char 'n' for normal aggregate, 'o' for ordered set function,
'h' for hypothetical-set function
with the parsing rules that given
agg( n fixed arguments ) WITHIN GROUP ( ORDER BY k sort specifications )
1. WITHIN GROUP is disallowed for normal aggregates.
2. For an ordered set function, n must equal aggnfixedargs. We treat all
n fixed arguments as contributing to the aggregate's result collation,
but ignore the sort arguments.
3. For a hypothetical-set function, n must equal aggnfixedargs plus k,
and we match up type and collation info of the last k fixed arguments
with the corresponding sort arguments. The first n-k fixed arguments
contribute to the aggregate's result collation, the rest not.
Reading back over this email, I see I've gone back and forth between
using the terms "direct args" and "fixed args" for the evaluate-once
stuff to the left of WITHIN GROUP. I guess I'm not really sold on
either terminology, but we need something we can use consistently
in the code and docs. The spec is no help, it has no generic term
at all for these args. Does anybody else have a preference, or
maybe another suggestion entirely?
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
Tom Lane-2 wrote
Further questions about WITHIN GROUP:
I believe that the spec requires that the "direct" arguments of an inverse
or hypothetical-set aggregate must not contain any Vars of the current
query level. They don't manage to say that in plain English, of course,
but in the
<hypothetical set function>
case the behavior is defined in
terms of this sub-select:( SELECT 0, SK1, ..., SKK
FROM TNAME UNION ALL
VALUES (1, VE1, ..., VEK) )where SKn are the sort key values, TNAME is an alias for the input table,
and VEn are the direct arguments. Any input-table Vars the aggregate
might refer to are thus in scope only for the SKn not the VEn. (This
definition also makes it clear that the VEn are to be evaluated once,
not once per row.) In the
<inverse distribution function>
case, they
resort to claiming that the value of the
<inverse distribution function
argument>
can be replaced by a numeric literal, which again makes it clear
that it's supposed to be evaluated just once.So that's all fine, but the patch seems a bit confused about it. If you
try to cheat, you get an error message that's less than apropos:regression=# select cume_dist(f1) within group(order by f1) from text_tbl
;
ERROR: column "text_tbl.f1" must appear in the GROUP BY clause or be used
in an aggregate function
LINE 1: select cume_dist(f1) within group(order by f1) from text_tbl...
^I'd have hoped for a message along the line of "fixed arguments of
cume_dist() must not contain variables", similar to the phrasing we
use when you try to put a variable in LIMIT.Also, there are some comments and code changes in nodeAgg.c that seem
to be on the wrong page here:+ /* + * 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; +The last two lines of that comment are new, all the rest was moved from
someplace else. Those last two lines are wrong according to the above
reasoning, and if they were correct the argument made in the pre-existing
part of the comment would be all wet, meaning the code could in fact crash
when trying to evaluate a Var reference in finalize_aggregates.So I'm inclined to undo this part of the patch (and anything else that's
rearranging logic with an eye to Var references in finalize_aggregates),
and to try to fix parse_agg.c so it gives a more specific error message
for this case.
The original design references the spec as allowing a column reference if it
is a group by key:
Select cume_dist(f1) within group ( order by f1 ) from text_tbl group by f1
No example was shown where this would be useful...but nonetheless the
comment and error both presume that such is true.
I would presume the implementation would only supply rows for sorting from
the single group in question so for practical purposes the columns have a
constant value for the entirety of the evaluation and so this makes sense
and acts just like partition by on a window aggregate.
Question, are there any tests that exercise this desired behavior? That
assuming agreement can be had that the group by behavior is indeed spec or
something custom we want to support.
David J.
--
View this message in context: http://postgresql.1045698.n5.nabble.com/Re-WITHIN-GROUP-patch-tp5773851p5782041.html
Sent from the PostgreSQL - hackers mailing list archive at Nabble.com.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
David Johnston <polobo@yahoo.com> writes:
The original design references the spec as allowing a column reference if it
is a group by key:
Select cume_dist(f1) within group ( order by f1 ) from text_tbl group by f1
No example was shown where this would be useful...but nonetheless the
comment and error both presume that such is true.
I can see no support for that position in the spec text, and as you say,
the usefulness is at best really debatable. Unless somebody can present a
credible use-case, or convince me that this is indeed what the spec says,
I'd be inclined to blow this off in favor of having a more intelligible
error message. (I think if you really did need such functionality, you
could get it with a sub-select.)
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
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> Further questions about WITHIN GROUP:
Tom> I believe that the spec requires that the "direct" arguments of
Tom> an inverse or hypothetical-set aggregate must not contain any
Tom> Vars of the current query level.
False.
The spec requires that the direct arguments must not contain _ungrouped_
columns (see <set function specification>), but nowhere are grouped
columns forbidden.
Tom> They don't manage to say that in plain English, of course, but
Tom> in the <hypothetical set function> case the behavior is defined
Tom> in terms of this sub-select:
Tom> ( SELECT 0, SK1, ..., SKK
Tom> FROM TNAME UNION ALL
Tom> VALUES (1, VE1, ..., VEK) )
"TNAME" there is not the input table or an alias for it, but rather
the specific subset of rows to which the grouping operation is being
applied (after applying a FILTER if any).
We're in the General Rules here, not the Syntax Rules, so this is
describing _how to compute the result_ rather than a syntactic
transformation of the input.
In any event, going by the docs on the web, Oracle does not forbid
grouped columns there (their wording is "This expr must be constant
within each aggregation group.") MSSQL seems to require a literal
constant, but that's obviously not per spec. IBM doesn't seem to
have it in db2 for linux, but some of their other products have it
and include examples of using grouped vars: see
So I'm going to say that the majority opinion is on my side here.
Tom> So that's all fine, but the patch seems a bit confused about it.
The patch treats vars in the direct args exactly as it would treat
them anywhere else where they must be ungrouped.
[snip a bunch of stuff based on false assumptions]
Tom> What I now think we should do about the added pg_aggregate
Tom> columns is to have:
Tom> aggnfixedargs int number of fixed arguments, excluding any
Tom> hypothetical ones (always 0 for normal aggregates)
Tom> aggkind char 'n' for normal aggregate,
Tom> 'o' for ordered set function,
Tom> 'h' for hypothetical-set function
I don't see any obvious problem with this.
Tom> with the parsing rules that given
Tom> agg( n fixed arguments ) WITHIN GROUP ( ORDER BY k sort specifications )
Tom> 1. WITHIN GROUP is disallowed for normal aggregates.
(This is what the submitted patch does.)
Tom> 2. For an ordered set function, n must equal aggnfixedargs. We
Tom> treat all n fixed arguments as contributing to the aggregate's
Tom> result collation, but ignore the sort arguments.
That doesn't work for getting a sensible collation out of
percentile_disc applied to a collatable type. (Which admittedly is an
extension to the spec, which allows only numeric and interval, but it
seems to me to be worth having.)
Tom> 3. For a hypothetical-set function, n must equal aggnfixedargs
Tom> plus k, and we match up type and collation info of the last k
Tom> fixed arguments with the corresponding sort arguments. The
Tom> first n-k fixed arguments contribute to the aggregate's result
Tom> collation, the rest not.
The submitted patch does essentially this but taking the number of
non-variadic args in place of the suggested aggnfixedargs. Presumably
in your version the latter would be derived from the former?
Tom> Reading back over this email, I see I've gone back and forth
Tom> between using the terms "direct args" and "fixed args" for the
Tom> evaluate-once stuff to the left of WITHIN GROUP. I guess I'm
Tom> not really sold on either terminology, but we need something we
Tom> can use consistently in the code and docs. The spec is no help,
Tom> it has no generic term at all for these args. Does anybody else
Tom> have a preference, or maybe another suggestion entirely?
We (Atri and I) have been using "direct args", but personally I'm not
amazingly happy with it. Documentation for other dbs tends to just call
them "arguments", and refer to the WITHIN GROUP expressions as "ordering
expressions" or similar.
--
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
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> I believe that the spec requires that the "direct" arguments of
Tom> an inverse or hypothetical-set aggregate must not contain any
Tom> Vars of the current query level.
In any event, going by the docs on the web, Oracle does not forbid
grouped columns there (their wording is "This expr must be constant
within each aggregation group.") MSSQL seems to require a literal
constant, but that's obviously not per spec. IBM doesn't seem to
have it in db2 for linux, but some of their other products have it
and include examples of using grouped vars: see
http://pic.dhe.ibm.com/infocenter/ntz/v7r0m3/index.jsp?topic=%2Fcom.ibm.nz.dbu.doc%2Fc_dbuser_hypothetical_set_family_syntax.html
OK, that's reasonably convincing. I think we'll need a HINT or something
to clarify the error message, because it sure looks like those arguments
are "used in an aggregate function".
Tom> 2. For an ordered set function, n must equal aggnfixedargs. We
Tom> treat all n fixed arguments as contributing to the aggregate's
Tom> result collation, but ignore the sort arguments.
That doesn't work for getting a sensible collation out of
percentile_disc applied to a collatable type. (Which admittedly is an
extension to the spec, which allows only numeric and interval, but it
seems to me to be worth having.)
Meh. I don't think you can have that and also have the behavior that
multiple ORDER BY items aren't constrained to have the same collation;
at least not without some rule that amounts to a special case for
percentile_disc, which I'd resist.
Tom> 3. For a hypothetical-set function, n must equal aggnfixedargs
Tom> plus k, and we match up type and collation info of the last k
Tom> fixed arguments with the corresponding sort arguments. The
Tom> first n-k fixed arguments contribute to the aggregate's result
Tom> collation, the rest not.
The submitted patch does essentially this but taking the number of
non-variadic args in place of the suggested aggnfixedargs. Presumably
in your version the latter would be derived from the former?
I'm not on board with using variadic vs non variadic to determine this.
For example, imagine a hypothetical-set function that for some reason
supports only a single sort column; there would be no reason to use
VARIADIC in its definition, and indeed good reason not to. In any
case, I don't think this behavior should be tied to implementation details
of the representation of the function signature, and IMV variadic is
just that --- particularly for VARIADIC ANY, which is nothing more than
a short-cut for overloading the function name with different numbers of
ANY arguments. Once we've got a match that involves N direct arguments
and K ordering arguments, the behavior should be determinate without
respect to just how we got that match.
Tom> Reading back over this email, I see I've gone back and forth
Tom> between using the terms "direct args" and "fixed args" for the
Tom> evaluate-once stuff to the left of WITHIN GROUP. I guess I'm
Tom> not really sold on either terminology, but we need something we
Tom> can use consistently in the code and docs. The spec is no help,
Tom> it has no generic term at all for these args. Does anybody else
Tom> have a preference, or maybe another suggestion entirely?
We (Atri and I) have been using "direct args", but personally I'm not
amazingly happy with it. Documentation for other dbs tends to just call
them "arguments", and refer to the WITHIN GROUP expressions as "ordering
expressions" or similar.
Well, given that I was mistaken to think there could be no Vars at all
in them, "fixed" may not be le mot juste. Unless somebody's got an
alternative to "direct", let's go with that.
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
I don't especially like the syntax you invented for listing arguments in
CREATE AGGREGATE, especially not the WITHIN GROUP (*) special case.
If we stick with that we're going to need to touch a lot more places than
you have, notably regprocedure. I also fear that this syntax is not
appropriate for identifying aggregates reliably, ie, aggregate argument
lists that look different in this syntax could reduce to identical
pg_proc.proargs lists, and perhaps vice versa.
I think we should just have it list the arguments as they'd appear in
pg_proc, and rely on aggregate properties (to wit, aggkind and
aggndirectargs) to identify ordered-set and hypothetical aggregates.
A slightly different question is what \da ought to print. I can't
say I think that (VARIADIC "any") WITHIN GROUP (*) is going to prove
very helpful to users, but probably just (VARIADIC "any") isn't
going to do either, at least not unless we add an aggregate-kind
column to the printout, and maybe not even then. It might work to
cheat by duplicating the last item if it's variadic:
(..., VARIADIC "any") WITHIN GROUP (VARIADIC "any")
while if it's not variadic, we'd have to work out which argument
positions correspond to the ordered-set arguments and put them
in the right places.
Regardless of that, though ... what is the reasoning for inventing
pg_get_aggregate_arguments() rather than just making
pg_get_function_arguments() do the right thing? The separate function
seems to accomplish little except complicating life for clients, eg in
psql's describe.c.
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
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> Regardless of that, though ... what is the reasoning for
Tom> inventing pg_get_aggregate_arguments() rather than just making
Tom> pg_get_function_arguments() do the right thing?
pg_get_function_arguments()'s interface assumes that the caller is
providing the enclosing parens. Changing it would have meant returning
a result like 'float8) WITHIN GROUP (float8' which I reckoned would
have too much chance of breaking existing callers.
--
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
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> Regardless of that, though ... what is the reasoning for
Tom> inventing pg_get_aggregate_arguments() rather than just making
Tom> pg_get_function_arguments() do the right thing?
pg_get_function_arguments()'s interface assumes that the caller is
providing the enclosing parens. Changing it would have meant returning
a result like 'float8) WITHIN GROUP (float8' which I reckoned would
have too much chance of breaking existing callers.
Well, as it stands, existing callers are broken a fortiori; they can't
possibly produce the right output for an ordered-set aggregate, if we
define the "right output" as looking like that. Of course, if we define
the right output as being just the arguments according to pg_proc, it's
fine. But your point about the parens is a good one anyway, because now
that I think about it, what \da has traditionally printed is
pg_catalog | sum | bigint | integer | sum as bigint acro
ss all integer input values
and I see the patch makes it do this
pg_catalog | sum | bigint | (integer) | sum as bigint acro
which I cannot find to be an improvement, especially since \df does
not look like that. (As patched, the output of "\df ran*" is positively
schizophrenic.)
One possibility is to forget all the parens and say that the display
looks like
type1, type2 WITHIN GROUP type3, type4
Please don't object that that doesn't look exactly like the syntax
for calling the function, because it doesn't anyway --- remember you
also need ORDER BY in the call.
Or perhaps we could abbreviate that --- maybe just WITHIN? Abbreviation
would be a good thing, especially if we're going to say 'VARIADIC "any"'
twice in common cases. OTOH I'm not sure we can make that work as a
declaration syntax without reserving WITHIN. I think WITHIN GROUP would
work, though I've not tried to see if bison likes it.
Anyway, the long and the short of this is that the SQL committee's bizarre
choice of syntax for calling these functions should not be followed too
slavishly when declaring them.
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
I wrote:
One possibility is to forget all the parens and say that the display
looks like
type1, type2 WITHIN GROUP type3, type4
Please don't object that that doesn't look exactly like the syntax
for calling the function, because it doesn't anyway --- remember you
also need ORDER BY in the call.
Actually, now that I think of it, why not use this syntax for declaration
and display purposes:
type1, type2 ORDER BY type3, type4
This has nearly as much relationship to the actual calling syntax as the
WITHIN GROUP proposal does, and it's hugely saner from a semantic
standpoint, because after all the ordering columns are ordering columns,
not grouping columns.
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
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Please don't object that that doesn't look exactly like the syntax
for calling the function, because it doesn't anyway --- remember
you also need ORDER BY in the call.
Tom> Actually, now that I think of it, why not use this syntax for
Tom> declaration and display purposes:
Tom> type1, type2 ORDER BY type3, type4
Tom> This has nearly as much relationship to the actual calling
Tom> syntax as the WITHIN GROUP proposal does,
But unfortunately it looks exactly like the calling sequence for a
normal aggregate with an order by clause - I really think that is
potentially too much confusion. (It's one thing not to look like
the calling syntax, it's another to look exactly like a valid
calling sequence for doing something _different_.)
--
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
Andrew Gierth wrote
"Tom" == Tom Lane <
tgl@.pa
> writes:
Please don't object that that doesn't look exactly like the syntax
for calling the function, because it doesn't anyway --- remember
you also need ORDER BY in the call.Tom> Actually, now that I think of it, why not use this syntax for
Tom> declaration and display purposes:Tom> type1, type2 ORDER BY type3, type4
Tom> This has nearly as much relationship to the actual calling
Tom> syntax as the WITHIN GROUP proposal does,But unfortunately it looks exactly like the calling sequence for a
normal aggregate with an order by clause - I really think that is
potentially too much confusion. (It's one thing not to look like
the calling syntax, it's another to look exactly like a valid
calling sequence for doing something _different_.)--
Andrew (irc:RhodiumToad)
How about:
type1, type2 GROUP ORDER type3, type4
OR
GROUP type1, type2 ORDER type3, type4
David J.
--
View this message in context: http://postgresql.1045698.n5.nabble.com/Re-WITHIN-GROUP-patch-tp5773851p5782202.html
Sent from the PostgreSQL - hackers mailing list archive at Nabble.com.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> Actually, now that I think of it, why not use this syntax for
Tom> declaration and display purposes:
Tom> type1, type2 ORDER BY type3, type4
But unfortunately it looks exactly like the calling sequence for a
normal aggregate with an order by clause - I really think that is
potentially too much confusion.
I thought about that too, but really that ship sailed long ago, and it
went to sea under the SQL committee's captaincy, so it's not our fault.
There are already at least four different standards-blessed ways you can
use ORDER BY in a query, some of them quite nearby (eg window functions);
so the potential for confusion is there no matter what we do. In this
case, if we describe ordered-set aggregates using WITHIN GROUP rather than
ORDER BY, we might avoid confusion with the normal-aggregate case, but
instead we will have confusion about what the arguments even do. Is
semantic confusion better than syntactic confusion?
Another thing to think about here is to wonder why the committee chose
anything as verbose as "agg(...) WITHIN GROUP (ORDER BY ...)" in the
first place. The words ORDER BY certainly seem pretty unnecessary.
I'm suspicious that they might've been leaving the door open to put other
things into the second set of parens later --- GROUP BY, maybe? So down
the road, we might regret it if we key off WITHIN GROUP and not ORDER BY.
Having said that, I'm not so dead set on it that I won't take WITHIN GROUP
if that's what more people want. But we gotta lose the extra parens; they
are just too strange for function declaration/documentation purposes.
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
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
pg_get_function_arguments()'s interface assumes that the caller is
providing the enclosing parens. Changing it would have meant
returning a result like 'float8) WITHIN GROUP (float8' which I
reckoned would have too much chance of breaking existing callers.
Tom> Well, as it stands, existing callers are broken a fortiori;
Not in most cases, because using the output of
pg_get_function_arguments works in all contexts except for
constructing the CREATE AGGREGATE statement (for example, a function
that uses the pg_get_function_arguments output to construct GRANTs
or ALTER OWNERs would still work).
And since constructing a correct CREATE AGGREGATE statement for an
ordered set function requires that the caller know about the
additional options to supply in the body, this does not seem like a
restriction.
Tom> Of course, if we define the right output as being just the
Tom> arguments according to pg_proc, it's fine.
The patch accepts the 'just the arguments according to pg_proc' as
input everywhere except in CREATE AGGREGATE, in addition to the
syntax with explicit WITHIN GROUP.
(With the exception of GRANT, as it happens, where since there is no
existing GRANT ON AGGREGATE variant and the patch doesn't add one,
only the pg_proc arguments form is accepted.)
Tom> But your point about the parens is a good one anyway, because
Tom> now that I think about it, what \da has traditionally printed is
Tom> pg_catalog | sum | bigint | integer | sum as bigint acro
Tom> ss all integer input values
Tom> and I see the patch makes it do this
Tom> pg_catalog | sum | bigint | (integer) | sum as bigint acro
Tom> which I cannot find to be an improvement, especially since \df
Tom> does not look like that. (As patched, the output of "\df ran*"
Tom> is positively schizophrenic.)
Since I don't particularly trust my own judgement on aesthetics, I used
the feedback I got from others when deciding what to do. Frankly I think
this one needs wider input than just you and me arguing over it.
--
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
On 12/06/2013 01:30 PM, Andrew Gierth wrote:
Since I don't particularly trust my own judgement on aesthetics, I used
the feedback I got from others when deciding what to do. Frankly I think
this one needs wider input than just you and me arguing over it.
Can someone paste examples of the two syntax alternatives we're talking
about here? I've lost track.
--
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
Import Notes
Reply to msg id not found: WMc3b5b58a670d5d10f1543bea1f99ed45d332ada42b9f984c8ab0a4900210a56923cc01f79d6b1415668affb0aa4c522e@asav-2.01.comReference msg id not found: WMc3b5b58a670d5d10f1543bea1f99ed45d332ada42b9f984c8ab0a4900210a56923cc01f79d6b1415668affb0aa4c522e@asav-2.01.com | Resolved by subject fallback
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> Another thing to think about here is to wonder why the committee chose
Tom> anything as verbose as "agg(...) WITHIN GROUP (ORDER BY ...)" in the
Tom> first place. The words ORDER BY certainly seem pretty unnecessary.
All of the ordered-set functions that the spec defines are intimately tied
to ordering of values, and they allow things like DESC ordering to affect
the results, so it seems obvious to me that they used (ORDER BY ...) because
what follows is both syntactically and semantically similar to any other use
of ORDER BY. (Similar logic seems to have been used for "FILTER (WHERE ...)".)
(The name "ordered set function" also seems quite specific.)
So I don't think there's any greater chance of the spec people adding
a WITHIN GROUP (something_other_than_order_by) construct than of them
adding any other awkward piece of syntax - and if they did, it would
represent an entirely new class of aggregate functions, separate from
ordered set functions and no doubt with its own headaches.
(I realize that expecting sanity from the spec writers is perhaps unwise)
--
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
Josh Berkus <josh@agliodbs.com> writes:
Can someone paste examples of the two syntax alternatives we're talking
about here? I've lost track.
I'll leave it to Andrew to describe/defend exactly what his patch is
doing. The alternative I'm thinking about is that in CREATE AGGREGATE
as well as \da output, the argument list of an ordered-set aggregate
would look like
type1, type2, ... ORDER BY type3, type4, ...
if the aggregate only permits a fixed number of ordering columns
(as mode() does for example). If it permits a variable number of
ordering columns, you could have
type1, type2, ... ORDER BY [ type3, type4, ... ] VARIADIC something
99% of the time the right-hand part would just be "VARIADIC ANY"
but if an aggregate had need to lock down the ordering column types,
or the leading ordering column types, it could do that. If it needs
a variable number of direct arguments as well (which in particular
hypothetical set functions do) then you would write
[ type1, type2, ... ] VARIADIC something ORDER BY VARIADIC something
where we constrain the two "somethings" to be the same. (Again, these
would *usually* be ANY, but I can envision that an aggregate might want to
constrain the argument types more than that.) We have to constrain this
case because the underlying pg_proc representation and parser function
lookup code only allow the last argument to be declared VARIADIC. So
under the hood, this last case would be represented in pg_proc with
proargs looking like just "[ type1, type2, ... ] VARIADIC something",
whereas in the first two cases the proargs representation would contain
all the same entries as above.
We could substitute something else for ORDER BY without doing a lot
of violence to this, if people are really down on that aspect.
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
"Josh" == Josh Berkus <josh@agliodbs.com> writes:
Since I don't particularly trust my own judgement on aesthetics, I
used the feedback I got from others when deciding what to
do. Frankly I think this one needs wider input than just you and
me arguing over it.
Josh> Can someone paste examples of the two syntax alternatives we're
Josh> talking about here? I've lost track.
OK. The starting point is that this is the calling syntax for ordered
set funcs, set by the spec:
SELECT func(foo) WITHIN GROUP (ORDER BY bar) FROM ...
where "foo" and "bar" might be fixed types, or polymorphic or variadic.
So we need to define (with no help from the spec) at least these:
- what syntax is used in CREATE AGGREGATE to specify the number and
types of parameters for a newly defined "func"
- what syntax is used to refer to the function in a
GRANT ... ON FUNCTION ... statement, or ALTER ... OWNER TO ...
- what should ::regprocedure casts accept as input and produce as
output
- what output is shown in \df and \da when listing the function's
argument types
The patch as submitted answers those questions as follows:
CREATE AGGREGATE func(integer) WITHIN GROUP (text) ...
GRANT ... ON FUNCTION func(integer,text) ...
(there is no GRANT ... ON AGGREGATE ... (yet))
ALTER AGGREGATE func(integer) WITHIN GROUP (text) OWNER TO ...
ALTER AGGREGATE func(integer,text) OWNER TO ...
ALTER FUNCTION func(integer,text) OWNER TO ...
(all three of those are equivalent)
regprocedure outputs 'func(integer,text)' and accepts only that as
input
postgres=# \df func
List of functions
Schema | Name | Result data type | Argument data types | Type
--------+------+------------------+-------------------------------+------
public | func | text | (integer) WITHIN GROUP (text) | agg
(1 row)
If there's also a func() that isn't an ordered set function, you get
output like this (which provoked tom's "schitzophrenic" comment):
postgres=# \df ran[a-z]{1,5}
List of functions
Schema | Name | Result data type | Argument data types | Type
------------+----------+------------------+-----------------------------------+--------
pg_catalog | random | double precision | | normal
pg_catalog | rangesel | double precision | internal, oid, internal, integer | normal
pg_catalog | rank | bigint | | window
pg_catalog | rank | bigint | (VARIADIC "any") WITHIN GROUP (*) | agg
(4 rows)
--
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
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
The patch as submitted answers those questions as follows:
CREATE AGGREGATE func(integer) WITHIN GROUP (text) ...
You've glossed over a significant amount of complexity, as shown by your
example that prints WITHIN GROUP (*), a syntax that you've not defined
here.
In the long run we might think it worthwhile to actually store two
separate arglists for ordered-set aggregates; probably, pg_proc.proargs
would just describe the direct arguments and there'd be a second oidvector
in pg_aggregate that would describe the ORDER BY arguments. This'd let
them be independently VARIADIC, or not. I'm not proposing we do that
right now, because we don't have any use-cases that aren't sufficiently
handled by the hack of letting a single VARIADIC ANY entry cover both sets
of arguments. I'd like though that the external syntax not be something
that prevents that from ever happening, and I'm afraid that this (*)
business is cheating enough to be a problem.
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
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> 2. For an ordered set function, n must equal aggnfixedargs. We
Tom> treat all n fixed arguments as contributing to the aggregate's
Tom> result collation, but ignore the sort arguments.
That doesn't work for getting a sensible collation out of
percentile_disc applied to a collatable type. (Which admittedly is
an extension to the spec, which allows only numeric and interval,
but it seems to me to be worth having.)
Tom> Meh. I don't think you can have that and also have the behavior
Tom> that multiple ORDER BY items aren't constrained to have the same
Tom> collation; at least not without some rule that amounts to a
Tom> special case for percentile_disc, which I'd resist.
What the submitted patch does (as discussed in the comment in
parse_collate) is to treat the sort argument as contributing to the
collation only if there is exactly one sort arg.
Consider a construct like:
select max(common_val)
from (select mode() within group (order by textcol) as common_val
from ... group by othercol) s;
(the same arguments for percentile_disc also apply to mode() and to
any other ordered set function that returns a value chosen from its
input sorted set)
Having this sort of thing not preserve the collation of textcol (or
fail) would be, IMO, surprising and undesirable.
--
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
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> Meh. I don't think you can have that and also have the behavior
Tom> that multiple ORDER BY items aren't constrained to have the same
Tom> collation; at least not without some rule that amounts to a
Tom> special case for percentile_disc, which I'd resist.
What the submitted patch does (as discussed in the comment in
parse_collate) is to treat the sort argument as contributing to the
collation only if there is exactly one sort arg.
Blech :-(
Not wanting to consider the sort args when there's more than one doesn't
square with forcing them to be considered when there's just one.
It's the same aggregate after all, and in general the semantics ought
to be the same whether you give one sort col or three.
We might be able to make this work sanely by considering only argument
positions that were declared something other than ANY (anyelement being
other than that, so this isn't leaving polymorphics in the cold entirely).
This is a bit unlike the normal rules for collation assignment but
it doesn't result in weird semantics changes depending on how many sort
columns you supply.
Consider a construct like:
select max(common_val)
from (select mode() within group (order by textcol) as common_val
from ... group by othercol) s;
AFAICT none of the SQL-spec aggregates expose the kind of case I'm worried
about, because none of the ones that can take multiple sort columns have a
potentially collatable return type. I don't think that justifies not
thinking ahead, though.
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
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> Not wanting to consider the sort args when there's more than one
Tom> doesn't square with forcing them to be considered when there's
Tom> just one. It's the same aggregate after all,
This logic is only applied in the patch to aggregates that _aren't_
hypothetical.
(thinking out loud:) It might be more consistent to also add the
condition that the single sort column not be a variadic arg. And/or
the condition that it be the same type as the result. Or have a flag
in pg_aggregate to say "this agg returns one of its sorted input
values, so preserve the collation".
Consider a construct like:
select max(common_val)
from (select mode() within group (order by textcol) as common_val
from ... group by othercol) s;
Tom> AFAICT none of the SQL-spec aggregates expose the kind of case
Tom> I'm worried about, because none of the ones that can take
Tom> multiple sort columns have a potentially collatable return type.
None of the spec's ordered-set functions expose any collation issue at
all, because they _all_ have non-collatable return types, period.
The problem only arises from the desire to make functions like
percentile_disc and mode applicable to collatable types.
--
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
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> I believe that the spec requires that the "direct" arguments of
Tom> an inverse or hypothetical-set aggregate must not contain any
Tom> Vars of the current query level.
False.
After examining this more closely, ISTM that the direct arguments are
supposed to be processed as if they weren't inside an aggregate call at all.
That being the case, isn't it flat out wrong for check_agg_arguments()
to be examining the agg_ordset list? It should ignore those expressions
whilst determining the aggregate's semantic level. As an example, an
upper-level Var in those expressions isn't grounds for deciding that the
aggregate isn't of the current query level.
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
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> After examining this more closely, ISTM that the direct
Tom> arguments are supposed to be processed as if they weren't inside
Tom> an aggregate call at all. That being the case, isn't it flat
Tom> out wrong for check_agg_arguments() to be examining the
Tom> agg_ordset list? It should ignore those expressions whilst
Tom> determining the aggregate's semantic level. As an example, an
Tom> upper-level Var in those expressions isn't grounds for deciding
Tom> that the aggregate isn't of the current query level.
Hmm... yes, you're probably right; but we'd still have to check somewhere
for improper nesting, no? since not even the direct args are allowed to
contain nested aggregate calls.
--
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
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> After examining this more closely, ISTM that the direct
Tom> arguments are supposed to be processed as if they weren't inside
Tom> an aggregate call at all. That being the case, isn't it flat
Tom> out wrong for check_agg_arguments() to be examining the
Tom> agg_ordset list? It should ignore those expressions whilst
Tom> determining the aggregate's semantic level. As an example, an
Tom> upper-level Var in those expressions isn't grounds for deciding
Tom> that the aggregate isn't of the current query level.
Hmm... yes, you're probably right; but we'd still have to check somewhere
for improper nesting, no? since not even the direct args are allowed to
contain nested aggregate calls.
Yeah, I had come to that same conclusion while making the changes;
actually, check_agg_arguments needs to look for aggs but not vars there.
In principle we could probably support aggs there if we really wanted to;
but it would result in evaluation-order dependencies among the aggs of a
single query level, which doesn't seem like something we want to take on
for a feature that's not required by spec.
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
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Hmm... yes, you're probably right; but we'd still have to check
somewhere for improper nesting, no? since not even the direct args
are allowed to contain nested aggregate calls.
Tom> Yeah, I had come to that same conclusion while making the
Tom> changes; actually, check_agg_arguments needs to look for aggs
Tom> but not vars there.
There's also the question of ungrouped vars, maybe. Consider these two
queries:
select array(select a+sum(x) from (values (0.3),(0.7)) v(a) group by a)
from generate_series(1,5) g(x);
select array(select percentile_disc(a) within group (order by x)
from (values (0.3),(0.7)) v(a) group by a)
from generate_series(1,5) g(x);
In both cases the aggregation query is the outer one; but while the first
can return a value, I think the second one has to fail (at least I can't
see any reasonable way of executing it).
--
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
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
There's also the question of ungrouped vars, maybe. Consider these two
queries:
select array(select a+sum(x) from (values (0.3),(0.7)) v(a) group by a)
from generate_series(1,5) g(x);
select array(select percentile_disc(a) within group (order by x)
from (values (0.3),(0.7)) v(a) group by a)
from generate_series(1,5) g(x);
In both cases the aggregation query is the outer one; but while the first
can return a value, I think the second one has to fail (at least I can't
see any reasonable way of executing it).
Hm, interesting. So having decided that the agg has level 1, we need to
reject any level-0 vars in the direct parameters, grouped or not.
We could alternatively decide that the agg has level 0, but that doesn't
seem terribly useful, and I think it's not per spec either. SQL:2008
section 6.9 <set function specification> seems pretty clear that
only aggregated arguments should be considered when determining the
semantic level of an aggregate. OTOH, I don't see any text there
restricting what can be in the non-aggregated arguments, so maybe the
committee thinks this case is sensible? Or they just missed it.
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
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> We could alternatively decide that the agg has level 0, but that
Tom> doesn't seem terribly useful, and I think it's not per spec
Tom> either. SQL:2008 section 6.9 <set function specification> seems
Tom> pretty clear that only aggregated arguments should be considered
Tom> when determining the semantic level of an aggregate. OTOH, I
Tom> don't see any text there restricting what can be in the
Tom> non-aggregated arguments, so maybe the committee thinks this
Tom> case is sensible? Or they just missed it.
My bet is that they missed it, because there's another obvious
oversight there; it doesn't define column references in the FILTER
clause applied to an ordered set function as being aggregated column
references, yet it's clear that this must be the case (since they
filter the set of rows that the aggregated column references refer
to).
--
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
On 11/21/13, 5:04 AM, Atri Sharma wrote:
Please find attached the latest patch for WITHIN GROUP. This patch is
after fixing the merge conflicts.
I would like to see more explanations and examples in the documentation.
You introduce this feature with "Ordered set functions compute a single
result from an ordered set of input values." But string_agg, for
example, does that as well, so it's not clear how this is different.
Between ordered aggregates, window functions, and this new feature, it
can get pretty confusing. Also, the "hypothetical" part should perhaps
be explained in more detail. The tutorial part of the documentation
contains a nice introduction to window function. I suggest you add
something like that as well.
In func.sgml, please list the functions in alphabetical order.
Also, don't write "should" when you mean "must".
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
[ still hacking away at this patch ]
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> Not wanting to consider the sort args when there's more than one
Tom> doesn't square with forcing them to be considered when there's
Tom> just one. It's the same aggregate after all,
This logic is only applied in the patch to aggregates that _aren't_
hypothetical.
(thinking out loud:) It might be more consistent to also add the
condition that the single sort column not be a variadic arg. And/or
the condition that it be the same type as the result. Or have a flag
in pg_aggregate to say "this agg returns one of its sorted input
values, so preserve the collation".
I eventually decided that we were overthinking this problem. At least
for regular ordered-set aggregates, we can just deem that the collation
of the aggregate is indeterminate unless all the inputs (both direct and
aggregated) agree on the collation to use. This gives us the right
answer for all the standard aggregates, which have at most one collatable
input, and it's very unclear that anything more complicated would be an
improvement. I definitely didn't like the hack that was in there that'd
force a sort column to absorb a possibly-unrelated collation.
For hypotheticals, I agree after reading the spec text that we're supposed
to unify the collations of matching hypothetical and aggregated arguments
to determine the collation to use for sorting that column. I see that
the patch just leaves these columns out of the determination of the
aggregate's result collation. That's okay for the time being at least,
since we have no hypotheticals with collatable output types, but I wonder
if anyone wants to argue for some other rule (and if so, what)?
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
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> I eventually decided that we were overthinking this problem. At
Tom> least for regular ordered-set aggregates, we can just deem that
Tom> the collation of the aggregate is indeterminate unless all the
Tom> inputs (both direct and aggregated) agree on the collation to
Tom> use. This gives us the right answer for all the standard
Tom> aggregates, which have at most one collatable input, and it's
Tom> very unclear that anything more complicated would be an
Tom> improvement. I definitely didn't like the hack that was in
Tom> there that'd force a sort column to absorb a possibly-unrelated
Tom> collation.
Yeah, I can go along with that, but see below.
Tom> For hypotheticals, I agree after reading the spec text that
Tom> we're supposed to unify the collations of matching hypothetical
Tom> and aggregated arguments to determine the collation to use for
Tom> sorting that column.
Yeah, the spec seemed clear enough on that.
Tom> I see that the patch just leaves these columns out of the
Tom> determination of the aggregate's result collation. That's okay
Tom> for the time being at least, since we have no hypotheticals with
Tom> collatable output types, but I wonder if anyone wants to argue
Tom> for some other rule (and if so, what)?
Any alternative seems a bit ad-hoc to me.
The examples I've thought of which would return collatable types are
all ones that would be implemented as plain ordered set functions even
if their logic was in some sense hypothetical. For example you could
envisage a value_prec(x) within group (order by y) that returns the
value of y which sorts immediately before x, but this would just be
declared as value_prec(anyelement) within group (anyelement) rather
than engaging the hypothetical argument stuff. (It's this sort of
thing that suggested pushing down the collation into the sort column
even for non-hypothetical ordered set functions.)
--
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
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
The examples I've thought of which would return collatable types are
all ones that would be implemented as plain ordered set functions even
if their logic was in some sense hypothetical. For example you could
envisage a value_prec(x) within group (order by y) that returns the
value of y which sorts immediately before x, but this would just be
declared as value_prec(anyelement) within group (anyelement) rather
than engaging the hypothetical argument stuff. (It's this sort of
thing that suggested pushing down the collation into the sort column
even for non-hypothetical ordered set functions.)
Meh. I see no very good reason that we shouldn't insist that the
collation be set on the sort column rather than the other input.
That is, if the alternatives are
value_prec(x collate "foo") within group (order by y)
value_prec(x) within group (order by y collate "foo")
which one makes it clearer that the ordering is to use collation foo?
The first one is at best unnatural, and at worst action-at-a-distance.
If you think of the sorting as an operation done in advance of
applying value_prec(), which is something the syntax rather encourages
you to think, there's no reason that it should absorb a collation
from a collate clause attached to a higher-level operation.
So I think the spec authors arguably got it wrong for hypotheticals,
and I'm uneager to extrapolate their choice of behavior into
situations where we don't know for certain that the collations of two
arguments must get unified.
More practically, I'm dubious about your assumption that an aggregate
like this needn't be marked hypothetical. I see that use of
anyelement would be enough to constrain the types to be the same,
but it doesn't ordinarily constrain collations, and I don't think
it should start doing so. So my reaction to this example is not
that we should hack the behavior for plain ordered-set aggregates,
but that we ought to find a rule that allows result-collation
determination for hypotheticals. We speculated upthread about
"merge the collations normally, but ignore inputs declared ANY"
and "merge the collations normally, but ignore variadic inputs".
Either of those would get the job done in this example. I kinda
think we should pick one of these rules and move on.
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
I wrote:
... So my reaction to this example is not
that we should hack the behavior for plain ordered-set aggregates,
but that we ought to find a rule that allows result-collation
determination for hypotheticals. We speculated upthread about
"merge the collations normally, but ignore inputs declared ANY"
and "merge the collations normally, but ignore variadic inputs".
Either of those would get the job done in this example. I kinda
think we should pick one of these rules and move on.
Or, really, why don't we just do the same thing I'm advocating for
the plain-ordered-set case? That is, if there's a single collation
applying to all the collatable inputs, that's the collation to use
for the aggregate; otherwise it has no determinate collation, and
it'll throw an error at runtime if it needs one. We realized long
ago that we can't throw most need-a-collation errors at parse time,
because the parser lacks information about which functions need to
know a collation to use. This seems to be in the same category.
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
I wrote:
Or, really, why don't we just do the same thing I'm advocating for
the plain-ordered-set case? That is, if there's a single collation
applying to all the collatable inputs, that's the collation to use
for the aggregate; otherwise it has no determinate collation, and
it'll throw an error at runtime if it needs one.
I went and tried to implement this, and realized that it would take some
pretty significant rewriting of parse_collate.c, because of examples
like this:
rank(a,b) within group (order by c collate "foo", d collate "bar")
In the current parse_collate logic, it would throw error immediately
upon being told to merge the two explicit-COLLATE results. We'd
need a way to postpone that error and instead just decide that the
rank aggregate's collation is indeterminate. While that's perhaps
just a SMOP, it would mean that ordered-set aggregates don't resolve
collation the same way as other functions, which pretty much destroys
the argument for this approach.
What's more, the same problem applies to non-hypothetical ordered-set
aggregates, if they've got more than one sortable input column.
What I'm now thinking we want to do is:
1. Non-hypothetical direct args always contribute to determining the
agg's collation.
2. Hypothetical and aggregated args contribute to the agg's collation
only if the agg is designed so that there is always exactly one
aggregated arg (ie, it's non-variadic with one aggregated arg).
Otherwise we assign their collations per-sort-column and don't merge
them into the aggregate's collation.
This specification ensures that a variadic aggregate doesn't change
behavior depending on how many sort columns there happen to be.
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
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> What I'm now thinking we want to do is:
Tom> 1. Non-hypothetical direct args always contribute to determining
Tom> the agg's collation.
Tom> 2. Hypothetical and aggregated args contribute to the agg's
Tom> collation only if the agg is designed so that there is always
Tom> exactly one aggregated arg (ie, it's non-variadic with one
Tom> aggregated arg). Otherwise we assign their collations
Tom> per-sort-column and don't merge them into the aggregate's
Tom> collation.
Tom> This specification ensures that a variadic aggregate doesn't
Tom> change behavior depending on how many sort columns there happen
Tom> to be.
Works for me.
--
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
Atri Sharma <atri.jiit@gmail.com> writes:
Please find attached the latest patch for WITHIN GROUP. This patch is
after fixing the merge conflicts.
I've committed this after significant editorialization --- most notably,
I pushed control of the sort step into the aggregate support functions.
I didn't like the way nodeAgg.c had been hacked up to do it the other way.
There's a couple hundred lines of code handling that in orderedsetaggs.c,
which is more or less comparable to the amount of code that didn't get
added to nodeAgg.c, so I think the argument that the original approach
avoided code bloat is bogus.
The main reason I pushed all the new aggregates into a single file
(orderedsetaggs.c) was so they could share a private typedef for the
transition state value. It's possible that we should expose that
struct so that third-party aggregate functions could leverage the
existing transition-function infrastructure instead of having to
copy-and-paste it. I wasn't sure where to put it though --- maybe
a new include file would be needed. Anyway I left the point for
future discussion.
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
Sent from my iPad
On 24-Dec-2013, at 2:50, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Atri Sharma <atri.jiit@gmail.com> writes:
Please find attached the latest patch for WITHIN GROUP. This patch is
after fixing the merge conflicts.I've committed this after significant editorialization --- most notably,
I pushed control of the sort step into the aggregate support functions.
I didn't like the way nodeAgg.c had been hacked up to do it the other way.
There's a couple hundred lines of code handling that in orderedsetaggs.c,
which is more or less comparable to the amount of code that didn't get
added to nodeAgg.c, so I think the argument that the original approach
avoided code bloat is bogus.The main reason I pushed all the new aggregates into a single file
(orderedsetaggs.c) was so they could share a private typedef for the
transition state value. It's possible that we should expose that
struct so that third-party aggregate functions could leverage the
existing transition-function infrastructure instead of having to
copy-and-paste it. I wasn't sure where to put it though --- maybe
a new include file would be needed. Anyway I left the point for
future discussion.regards, tom lane
Thank you!
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> I've committed this after significant editorialization --- most
Tom> notably, I pushed control of the sort step into the aggregate
Tom> support functions.
Initial tests suggest that your version is ~40% slower than ours on
some workloads.
On my system, this query takes ~950ms using our dev branch of the code,
and ~1050ms on git master (using \timing in psql for timings, and taking
the best of many consecutive runs):
select count(*)
from (select percentile_disc(0.5) within group (order by i)
from generate_series(1,3) i, generate_series(1,100000) j group by j) s;
About ~700ms of that is overhead, as tested by running this query with
enable_hashagg=false:
select count(*)
from (select j
from generate_series(1,3) i, generate_series(1,100000) j group by j) s;
So your version is taking 350ms for the percentile calculations
compared to 250ms for ours.
--
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
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> I've committed this after significant editorialization --- most
Tom> notably, I pushed control of the sort step into the aggregate
Tom> support functions.
Initial tests suggest that your version is ~40% slower than ours on
some workloads.
I poked at this a bit with perf and oprofile, and concluded that probably
the difference comes from ordered_set_startup() repeating lookups for each
group that could be done just once per query. I'm not sure I believe the
40% figure; on this particular test case, oprofile breaks down the costs
of ordered_set_startup() like this:
29 0.0756 postgres advance_transition_function
38307 99.9244 postgres ordered_set_transition
1445 1.0808 postgres ordered_set_startup
31418 79.4990 postgres tuplesort_begin_datum
4056 10.2632 postgres get_typlenbyvalalign
1445 3.6564 postgres ordered_set_startup [self]
734 1.8573 postgres MemoryContextAllocZero
510 1.2905 postgres RegisterExprContextCallback
283 0.7161 postgres exprType
247 0.6250 postgres get_sortgroupclause_tle
235 0.5946 postgres exprCollation
92 0.2328 postgres ReleaseSysCache
78 0.1974 postgres SearchSysCache
71 0.1797 postgres AggGetAggref
63 0.1594 postgres AggCheckCallContext
58 0.1468 postgres AllocSetAlloc
46 0.1164 postgres PrepareSortSupportFromOrderingOp
43 0.1088 postgres AggGetPerAggEContext
40 0.1012 postgres get_typlenbyval
39 0.0987 postgres palloc0
35 0.0886 postgres MemoryContextAlloc
17 0.0430 postgres get_sortgroupref_tle
10 0.0253 postgres tuplesort_begin_common
The tuplesort_begin_datum calls are needed regardless --- your version
was just doing them inside nodeAgg rather than externally. However,
get_typlenbyvalalign and some of the other calls here are to fetch
information that doesn't change across groups; probably we could arrange
to cache that info instead of recomputing it each time. Still, it doesn't
look like that could save more than 20% of ordered_set_startup, which
itself is still only a few percent of the total query time.
Looking at this example makes me wonder if it wouldn't be worthwhile to
provide a way to reset and re-use a tuplesort object, instead of redoing
all the lookup work involved. Or maybe just find a way to cache the
catalog lookups that are happening inside tuplesort_begin_datum, which are
about 50% of that function's cost it looks like. We're paying this same
kind of price for repeated tuplesort setup in the existing nodeAgg code,
if we have an aggregate with ORDER BY or DISTINCT in a grouped query with
many groups.
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
On Sun, Jan 5, 2014 at 12:00 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Looking at this example makes me wonder if it wouldn't be worthwhile to
provide a way to reset and re-use a tuplesort object, instead of redoing
all the lookup work involved. Or maybe just find a way to cache the
catalog lookups that are happening inside tuplesort_begin_datum, which are
about 50% of that function's cost it looks like. We're paying this same
kind of price for repeated tuplesort setup in the existing nodeAgg code,
if we have an aggregate with ORDER BY or DISTINCT in a grouped query with
many groups.
This sounds very similar to:
/messages/by-id/CAApHDvrbq348M8dYj-7O4VaE5PS9ZoQ_34rGvaaN1QYXL2SP_A@mail.gmail.com
A reset function was added in the next patch which improved performance in
my test case by about 5 times.
Perhaps they can make use of the same function.
Regards
David Rowley
Show quoted text
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
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Initial tests suggest that your version is ~40% slower than ours on
some workloads.
Tom> I poked at this a bit with perf and oprofile, and concluded that
Tom> probably the difference comes from ordered_set_startup()
Tom> repeating lookups for each group that could be done just once
Tom> per query.
Retesting with your changes shows that the gap is down to 15% but still
present:
work_mem=64MB enable_hashagg=off (for baseline test)
baseline query (333ms on both versions):
select count(*)
from (select j from generate_series(1,3) i,
generate_series(1,100000) j group by j) s;
test query:
select count(*)
from (select percentile_disc(0.5) within group (order by i)
from generate_series(1,3) i,
generate_series(1,100000) j group by j) s;
On the original patch as supplied: 571ms - 333ms = 238ms
On current master: 607ms - 333ms = 274ms
Furthermore, I can't help noticing that the increased complexity has
now pretty much negated your original arguments for moving so much of
the work out of nodeAgg.c.
--
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
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
Retesting with your changes shows that the gap is down to 15% but still
present:
There's probably some overhead from traversing advance_transition_function
for each row, which your version wouldn't have done. 15% sounds pretty
high for that though, since advance_transition_function doesn't have much
to do when the transition function is non strict and the state value is
pass-by-value (which "internal" is). It's possible that we could do
something to micro-optimize that case.
I also wondered if it was possible to omit rechecking AggCheckCallContext
after the first time through in ordered_set_transition(). It seemed
unsafe to perhaps not have that happen at all, since if the point is to
detect misuse then the mere fact that argument 0 isn't null isn't much
comfort. It strikes me though that now we could test for "first call" by
looking at fcinfo->flinfo->fn_extra, and be pretty sure that we've already
checked the context if that isn't null. Whether this would save anything
noticeable isn't clear though; I didn't see AggCheckCallContext high in
the profile.
Furthermore, I can't help noticing that the increased complexity has
now pretty much negated your original arguments for moving so much of
the work out of nodeAgg.c.
The key reason for that was, and remains, not having the behavior
hard-wired in nodeAgg; I believe that this design permits things to be
accomplished in aggregate implementation functions that would not have
been possible with the original patch. I'm willing to accept some code
growth to have that flexibility.
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
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Furthermore, I can't help noticing that the increased complexity
has now pretty much negated your original arguments for moving so
much of the work out of nodeAgg.c.
Tom> The key reason for that was, and remains, not having the
Tom> behavior hard-wired in nodeAgg; I believe that this design
Tom> permits things to be accomplished in aggregate implementation
Tom> functions that would not have been possible with the original
Tom> patch. I'm willing to accept some code growth to have that
Tom> flexibility.
Do you have an actual use case?
--
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
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
"Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
Tom> The key reason for that was, and remains, not having the
Tom> behavior hard-wired in nodeAgg; I believe that this design
Tom> permits things to be accomplished in aggregate implementation
Tom> functions that would not have been possible with the original
Tom> patch. I'm willing to accept some code growth to have that
Tom> flexibility.
Do you have an actual use case?
Not a concrete example of an aggregate to build, no; but it seemed
plausible to me that a new aggregate might want more control over
the sorting operation than was possible with your patch. Basically
the only flexibility that was there was to sort a hypothetical row before
or after its peers, right? Now it's got essentially full control over
the sort parameters.
One idea that comes to mind is that an aggregate that's interested in the
"top N" rows might wish to flip the sort order, so that the rows it wants
come out of the tuplesort first rather than last --- and/or it might want
to tell the tuplesort about the row limitation, so that the bounded-sort
logic can be used.
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
I wrote:
There's probably some overhead from traversing advance_transition_function
for each row, which your version wouldn't have done. 15% sounds pretty
high for that though, since advance_transition_function doesn't have much
to do when the transition function is non strict and the state value is
pass-by-value (which "internal" is). It's possible that we could do
something to micro-optimize that case.
The most obvious micro-optimization that's possible there is to avoid
redoing InitFunctionCallInfoData for each row. I tried this as in the
attached patch. It seems to be good for about half a percent overall
savings on your example case. That's not much, but then again this
helps for *all* aggregates, and many of them are cheaper than ordered
aggregates. I see about 2% overall savings on
select count(*) from generate_series(1,1000000);
which seems like a more interesting number.
As against that, the roughly-kilobyte-sized FunctionCallInfoData is
no longer just transient data on the stack, but persists for the lifespan
of the query. We pay that already for plain FuncExpr and OpExpr nodes,
though.
On balance, I'm inclined to apply this; the performance benefit may be
marginal but it seems like it makes advance_transition_function's API
a tad cleaner by reducing the number of distinct structs it's hacking
on. Comments?
regards, tom lane
Attachments:
nodeAgg-micro-optimization.patchtext/x-diff; charset=us-ascii; name=nodeAgg-micro-optimization.patchDownload
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 827b009..0dafb51 100644
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** typedef struct AggStatePerAggData
*** 235,240 ****
--- 235,248 ----
*/
Tuplesortstate *sortstate; /* sort object, if DISTINCT or ORDER BY */
+
+ /*
+ * This field is a pre-initialized FunctionCallInfo struct used for
+ * calling this aggregate's transfn. We save a few cycles per row by not
+ * re-initializing the unchanging fields; which isn't much, but it seems
+ * worth the extra space consumption.
+ */
+ FunctionCallInfoData transfn_fcinfo;
} AggStatePerAggData;
/*
*************** static void initialize_aggregates(AggSta
*** 290,297 ****
AggStatePerGroup pergroup);
static void advance_transition_function(AggState *aggstate,
AggStatePerAgg peraggstate,
! AggStatePerGroup pergroupstate,
! FunctionCallInfoData *fcinfo);
static void advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup);
static void process_ordered_aggregate_single(AggState *aggstate,
AggStatePerAgg peraggstate,
--- 298,304 ----
AggStatePerGroup pergroup);
static void advance_transition_function(AggState *aggstate,
AggStatePerAgg peraggstate,
! AggStatePerGroup pergroupstate);
static void advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup);
static void process_ordered_aggregate_single(AggState *aggstate,
AggStatePerAgg peraggstate,
*************** initialize_aggregates(AggState *aggstate
*** 399,419 ****
* Given new input value(s), advance the transition function of an aggregate.
*
* The new values (and null flags) have been preloaded into argument positions
! * 1 and up in fcinfo, so that we needn't copy them again to pass to the
! * transition function. No other fields of fcinfo are assumed valid.
*
* It doesn't matter which memory context this is called in.
*/
static void
advance_transition_function(AggState *aggstate,
AggStatePerAgg peraggstate,
! AggStatePerGroup pergroupstate,
! FunctionCallInfoData *fcinfo)
{
! int numTransInputs = peraggstate->numTransInputs;
MemoryContext oldContext;
Datum newVal;
- int i;
if (peraggstate->transfn.fn_strict)
{
--- 406,425 ----
* Given new input value(s), advance the transition function of an aggregate.
*
* The new values (and null flags) have been preloaded into argument positions
! * 1 and up in peraggstate->transfn_fcinfo, so that we needn't copy them again
! * to pass to the transition function. We also expect that the static fields
! * of the fcinfo are already initialized; that was done by ExecInitAgg().
*
* It doesn't matter which memory context this is called in.
*/
static void
advance_transition_function(AggState *aggstate,
AggStatePerAgg peraggstate,
! AggStatePerGroup pergroupstate)
{
! FunctionCallInfoData *fcinfo = &peraggstate->transfn_fcinfo;
MemoryContext oldContext;
Datum newVal;
if (peraggstate->transfn.fn_strict)
{
*************** advance_transition_function(AggState *ag
*** 421,426 ****
--- 427,435 ----
* For a strict transfn, nothing happens when there's a NULL input; we
* just keep the prior transValue.
*/
+ int numTransInputs = peraggstate->numTransInputs;
+ int i;
+
for (i = 1; i <= numTransInputs; i++)
{
if (fcinfo->argnull[i])
*************** advance_transition_function(AggState *ag
*** 467,478 ****
/*
* OK to call the transition function
*/
- InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
- numTransInputs + 1,
- peraggstate->aggCollation,
- (void *) aggstate, NULL);
fcinfo->arg[0] = pergroupstate->transValue;
fcinfo->argnull[0] = pergroupstate->transValueIsNull;
newVal = FunctionCallInvoke(fcinfo);
--- 476,484 ----
/*
* OK to call the transition function
*/
fcinfo->arg[0] = pergroupstate->transValue;
fcinfo->argnull[0] = pergroupstate->transValueIsNull;
+ fcinfo->isnull = false; /* just in case transfn doesn't set it */
newVal = FunctionCallInvoke(fcinfo);
*************** advance_aggregates(AggState *aggstate, A
*** 574,592 ****
else
{
/* We can apply the transition function immediately */
! FunctionCallInfoData fcinfo;
/* Load values into fcinfo */
/* Start from 1, since the 0th arg will be the transition value */
Assert(slot->tts_nvalid >= numTransInputs);
for (i = 0; i < numTransInputs; i++)
{
! fcinfo.arg[i + 1] = slot->tts_values[i];
! fcinfo.argnull[i + 1] = slot->tts_isnull[i];
}
! advance_transition_function(aggstate, peraggstate, pergroupstate,
! &fcinfo);
}
}
}
--- 580,597 ----
else
{
/* We can apply the transition function immediately */
! FunctionCallInfoData *fcinfo = &peraggstate->transfn_fcinfo;
/* Load values into fcinfo */
/* Start from 1, since the 0th arg will be the transition value */
Assert(slot->tts_nvalid >= numTransInputs);
for (i = 0; i < numTransInputs; i++)
{
! fcinfo->arg[i + 1] = slot->tts_values[i];
! fcinfo->argnull[i + 1] = slot->tts_isnull[i];
}
! advance_transition_function(aggstate, peraggstate, pergroupstate);
}
}
}
*************** process_ordered_aggregate_single(AggStat
*** 622,638 ****
MemoryContext workcontext = aggstate->tmpcontext->ecxt_per_tuple_memory;
MemoryContext oldContext;
bool isDistinct = (peraggstate->numDistinctCols > 0);
Datum *newVal;
bool *isNull;
- FunctionCallInfoData fcinfo;
Assert(peraggstate->numDistinctCols < 2);
tuplesort_performsort(peraggstate->sortstate);
/* Load the column into argument 1 (arg 0 will be transition value) */
! newVal = fcinfo.arg + 1;
! isNull = fcinfo.argnull + 1;
/*
* Note: if input type is pass-by-ref, the datums returned by the sort are
--- 627,643 ----
MemoryContext workcontext = aggstate->tmpcontext->ecxt_per_tuple_memory;
MemoryContext oldContext;
bool isDistinct = (peraggstate->numDistinctCols > 0);
+ FunctionCallInfoData *fcinfo = &peraggstate->transfn_fcinfo;
Datum *newVal;
bool *isNull;
Assert(peraggstate->numDistinctCols < 2);
tuplesort_performsort(peraggstate->sortstate);
/* Load the column into argument 1 (arg 0 will be transition value) */
! newVal = fcinfo->arg + 1;
! isNull = fcinfo->argnull + 1;
/*
* Note: if input type is pass-by-ref, the datums returned by the sort are
*************** process_ordered_aggregate_single(AggStat
*** 668,675 ****
}
else
{
! advance_transition_function(aggstate, peraggstate, pergroupstate,
! &fcinfo);
/* forget the old value, if any */
if (!oldIsNull && !peraggstate->inputtypeByVal)
pfree(DatumGetPointer(oldVal));
--- 673,679 ----
}
else
{
! advance_transition_function(aggstate, peraggstate, pergroupstate);
/* forget the old value, if any */
if (!oldIsNull && !peraggstate->inputtypeByVal)
pfree(DatumGetPointer(oldVal));
*************** process_ordered_aggregate_multi(AggState
*** 704,710 ****
AggStatePerGroup pergroupstate)
{
MemoryContext workcontext = aggstate->tmpcontext->ecxt_per_tuple_memory;
! FunctionCallInfoData fcinfo;
TupleTableSlot *slot1 = peraggstate->evalslot;
TupleTableSlot *slot2 = peraggstate->uniqslot;
int numTransInputs = peraggstate->numTransInputs;
--- 708,714 ----
AggStatePerGroup pergroupstate)
{
MemoryContext workcontext = aggstate->tmpcontext->ecxt_per_tuple_memory;
! FunctionCallInfoData *fcinfo = &peraggstate->transfn_fcinfo;
TupleTableSlot *slot1 = peraggstate->evalslot;
TupleTableSlot *slot2 = peraggstate->uniqslot;
int numTransInputs = peraggstate->numTransInputs;
*************** process_ordered_aggregate_multi(AggState
*** 739,750 ****
/* Start from 1, since the 0th arg will be the transition value */
for (i = 0; i < numTransInputs; i++)
{
! fcinfo.arg[i + 1] = slot1->tts_values[i];
! fcinfo.argnull[i + 1] = slot1->tts_isnull[i];
}
! advance_transition_function(aggstate, peraggstate, pergroupstate,
! &fcinfo);
if (numDistinctCols > 0)
{
--- 743,753 ----
/* Start from 1, since the 0th arg will be the transition value */
for (i = 0; i < numTransInputs; i++)
{
! fcinfo->arg[i + 1] = slot1->tts_values[i];
! fcinfo->argnull[i + 1] = slot1->tts_isnull[i];
}
! advance_transition_function(aggstate, peraggstate, pergroupstate);
if (numDistinctCols > 0)
{
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1799,1804 ****
--- 1802,1808 ----
&transfnexpr,
&finalfnexpr);
+ /* set up infrastructure for calling the transfn and finalfn */
fmgr_info(transfn_oid, &peraggstate->transfn);
fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1810,1815 ****
--- 1814,1826 ----
peraggstate->aggCollation = aggref->inputcollid;
+ InitFunctionCallInfoData(peraggstate->transfn_fcinfo,
+ &peraggstate->transfn,
+ peraggstate->numTransInputs + 1,
+ peraggstate->aggCollation,
+ (void *) aggstate, NULL);
+
+ /* get info about relevant datatypes */
get_typlenbyval(aggref->aggtype,
&peraggstate->resulttypeLen,
&peraggstate->resulttypeByVal);