From 108fe4730152058f9b576969d08898b39bf7fc38 Mon Sep 17 00:00:00 2001 From: Tom Lane <tgl@sss.pgh.pa.us> Date: Thu, 27 Jul 2006 19:52:07 +0000 Subject: [PATCH] Aggregate functions now support multiple input arguments. I also took the opportunity to treat COUNT(*) as a zero-argument aggregate instead of the old hack that equated it to COUNT(1); this is materially cleaner (no more weird ANYOID cases) and ought to be at least a tiny bit faster. Original patch by Sergey Koposov; review, documentation, simple regression tests, pg_dump and psql support by moi. --- doc/src/sgml/ref/alter_aggregate.sgml | 13 +- doc/src/sgml/ref/comment.sgml | 9 +- doc/src/sgml/ref/create_aggregate.sgml | 68 +++---- doc/src/sgml/ref/drop_aggregate.sgml | 11 +- doc/src/sgml/ref/psql-ref.sgml | 4 +- doc/src/sgml/sql.sgml | 24 +-- doc/src/sgml/syntax.sgml | 30 +-- doc/src/sgml/xaggr.sgml | 21 ++- src/backend/catalog/pg_aggregate.c | 114 ++++++----- src/backend/commands/aggregatecmds.c | 53 +++--- src/backend/executor/execQual.c | 7 +- src/backend/executor/nodeAgg.c | 178 ++++++++++++------ src/backend/nodes/copyfuncs.c | 4 +- src/backend/nodes/equalfuncs.c | 4 +- src/backend/nodes/outfuncs.c | 4 +- src/backend/nodes/readfuncs.c | 4 +- src/backend/optimizer/plan/planagg.c | 15 +- src/backend/optimizer/util/clauses.c | 58 ++++-- src/backend/parser/gram.y | 14 +- src/backend/parser/parse_agg.c | 56 +++--- src/backend/parser/parse_func.c | 54 +++--- src/backend/utils/adt/ruleutils.c | 24 ++- src/bin/pg_dump/pg_dump.c | 113 +++++------ src/bin/pg_dump/pg_dump.h | 5 +- src/bin/psql/describe.c | 19 +- src/include/catalog/catversion.h | 4 +- src/include/catalog/pg_aggregate.h | 13 +- src/include/catalog/pg_proc.h | 6 +- src/include/nodes/execnodes.h | 4 +- src/include/nodes/primnodes.h | 6 +- src/include/parser/parse_agg.h | 5 +- src/test/regress/expected/aggregates.out | 19 ++ .../regress/expected/create_aggregate.out | 27 ++- src/test/regress/expected/opr_sanity.out | 30 +-- src/test/regress/expected/polymorphism.out | 72 ++++--- src/test/regress/sql/aggregates.sql | 6 +- src/test/regress/sql/create_aggregate.sql | 31 ++- src/test/regress/sql/opr_sanity.sql | 24 ++- src/test/regress/sql/polymorphism.sql | 33 ++-- 39 files changed, 702 insertions(+), 484 deletions(-) diff --git a/doc/src/sgml/ref/alter_aggregate.sgml b/doc/src/sgml/ref/alter_aggregate.sgml index c3311b99ce7..a54969032c0 100644 --- a/doc/src/sgml/ref/alter_aggregate.sgml +++ b/doc/src/sgml/ref/alter_aggregate.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/ref/alter_aggregate.sgml,v 1.7 2005/10/13 22:44:51 tgl Exp $ +$PostgreSQL: pgsql/doc/src/sgml/ref/alter_aggregate.sgml,v 1.8 2006/07/27 19:52:04 tgl Exp $ PostgreSQL documentation --> @@ -20,9 +20,9 @@ PostgreSQL documentation <refsynopsisdiv> <synopsis> -ALTER AGGREGATE <replaceable>name</replaceable> ( <replaceable>type</replaceable> ) RENAME TO <replaceable>new_name</replaceable> -ALTER AGGREGATE <replaceable>name</replaceable> ( <replaceable>type</replaceable> ) OWNER TO <replaceable>new_owner</replaceable> -ALTER AGGREGATE <replaceable>name</replaceable> ( <replaceable>type</replaceable> ) SET SCHEMA <replaceable>new_schema</replaceable> +ALTER AGGREGATE <replaceable>name</replaceable> ( <replaceable>type</replaceable> [ , ... ] ) RENAME TO <replaceable>new_name</replaceable> +ALTER AGGREGATE <replaceable>name</replaceable> ( <replaceable>type</replaceable> [ , ... ] ) OWNER TO <replaceable>new_owner</replaceable> +ALTER AGGREGATE <replaceable>name</replaceable> ( <replaceable>type</replaceable> [ , ... ] ) SET SCHEMA <replaceable>new_schema</replaceable> </synopsis> </refsynopsisdiv> @@ -64,8 +64,9 @@ ALTER AGGREGATE <replaceable>name</replaceable> ( <replaceable>type</replaceable <term><replaceable class="parameter">type</replaceable></term> <listitem> <para> - The argument data type of the aggregate function, or - <literal>*</literal> if the function accepts any data type. + An input data type on which the aggregate function operates. + To reference a zero-argument aggregate function, write <literal>*</> + in place of the list of input data types. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml index d8c0a3acab0..c340b142c5b 100644 --- a/doc/src/sgml/ref/comment.sgml +++ b/doc/src/sgml/ref/comment.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/ref/comment.sgml,v 1.30 2006/02/12 03:22:17 momjian Exp $ +$PostgreSQL: pgsql/doc/src/sgml/ref/comment.sgml,v 1.31 2006/07/27 19:52:04 tgl Exp $ PostgreSQL documentation --> @@ -24,7 +24,7 @@ COMMENT 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">agg_type</replaceable>) | + AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> (<replaceable class="PARAMETER">agg_type</replaceable> [, ...] ) | CAST (<replaceable>sourcetype</replaceable> AS <replaceable>targettype</replaceable>) | CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ON <replaceable class="PARAMETER">table_name</replaceable> | CONVERSION <replaceable class="PARAMETER">object_name</replaceable> | @@ -101,8 +101,9 @@ COMMENT ON <term><replaceable class="parameter">agg_type</replaceable></term> <listitem> <para> - The argument data type of the aggregate function, or - <literal>*</literal> if the function accepts any data type. + An input data type on which the aggregate function operates. + To reference a zero-argument aggregate function, write <literal>*</> + in place of the list of input data types. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/create_aggregate.sgml b/doc/src/sgml/ref/create_aggregate.sgml index 5eb0741c9c2..b43636c9bfa 100644 --- a/doc/src/sgml/ref/create_aggregate.sgml +++ b/doc/src/sgml/ref/create_aggregate.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/ref/create_aggregate.sgml,v 1.34 2006/04/15 17:45:18 tgl Exp $ +$PostgreSQL: pgsql/doc/src/sgml/ref/create_aggregate.sgml,v 1.35 2006/07/27 19:52:04 tgl Exp $ PostgreSQL documentation --> @@ -20,7 +20,7 @@ PostgreSQL documentation <refsynopsisdiv> <synopsis> -CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> ( <replaceable class="PARAMETER">input_data_type</replaceable> ) ( +CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> ( <replaceable class="PARAMETER">input_data_type</replaceable> [ , ... ] ) ( SFUNC = <replaceable class="PARAMETER">sfunc</replaceable>, STYPE = <replaceable class="PARAMETER">state_data_type</replaceable> [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ] @@ -60,16 +60,16 @@ CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> ( </para> <para> - An aggregate function is identified by its name and input data type. + An aggregate function is identified by its name and input data type(s). Two aggregates in the same schema can have the same name if they operate on different input types. The - name and input data type of an aggregate must also be distinct from + name and input data type(s) of an aggregate must also be distinct from the name and input data type(s) of every ordinary function in the same schema. </para> <para> - An aggregate function is made from one or two ordinary + An aggregate function is made from one or two ordinary functions: a state transition function <replaceable class="PARAMETER">sfunc</replaceable>, @@ -77,7 +77,7 @@ CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> ( <replaceable class="PARAMETER">ffunc</replaceable>. These are used as follows: <programlisting> -<replaceable class="PARAMETER">sfunc</replaceable>( internal-state, next-data-item ) ---> next-internal-state +<replaceable class="PARAMETER">sfunc</replaceable>( internal-state, next-data-values ) ---> next-internal-state <replaceable class="PARAMETER">ffunc</replaceable>( internal-state ) ---> aggregate-value </programlisting> </para> @@ -85,10 +85,11 @@ CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> ( <para> <productname>PostgreSQL</productname> creates a temporary variable of data type <replaceable class="PARAMETER">stype</replaceable> - to hold the current internal state of the aggregate. At each input - data item, - the state transition function is invoked to calculate a new - internal state value. After all the data has been processed, + to hold the current internal state of the aggregate. At each input row, + the aggregate argument value(s) are calculated and + the state transition function is invoked with the current state value + and the new argument value(s) to calculate a new + internal state value. After all the rows have been processed, the final function is invoked once to calculate the aggregate's return value. If there is no final function then the ending state value is returned as-is. @@ -106,15 +107,16 @@ CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> ( <para> If the state transition function is declared <quote>strict</quote>, then it cannot be called with null inputs. With such a transition - function, aggregate execution behaves as follows. Null input values - are ignored (the function is not called and the previous state value - is retained). If the initial state value is null, then the first - nonnull input value replaces the state value, and the transition - function is invoked beginning with the second nonnull input value. + function, aggregate execution behaves as follows. Rows with any null input + values are ignored (the function is not called and the previous state value + is retained). If the initial state value is null, then at the first row + with all-nonnull input values, the first argument value replaces the state + value, and the transition function is invoked at subsequent rows with + all-nonnull input values. This is handy for implementing aggregates like <function>max</function>. Note that this behavior is only available when <replaceable class="PARAMETER">state_data_type</replaceable> - is the same as + is the same as the first <replaceable class="PARAMETER">input_data_type</replaceable>. When these types are different, you must supply a nonnull initial condition or use a nonstrict transition function. @@ -122,7 +124,7 @@ CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> ( <para> If the state transition function is not strict, then it will be called - unconditionally at each input value, and must deal with null inputs + unconditionally at each input row, and must deal with null inputs and null transition values for itself. This allows the aggregate author to have full control over the aggregate's handling of null values. </para> @@ -180,10 +182,10 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1; <term><replaceable class="PARAMETER">input_data_type</replaceable></term> <listitem> <para> - The input data type on which this aggregate function operates. - This can be specified as <literal>*</> for an aggregate that - does not examine its input values (an example is - <function>count(*)</function>). + An input data type on which this aggregate function operates. + To create a zero-argument aggregate function, write <literal>*</> + in place of the list of input data types. (An example of such an + aggregate is <function>count(*)</function>.) </para> </listitem> </varlistentry> @@ -195,8 +197,8 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1; In the old syntax for <command>CREATE AGGREGATE</>, the input data type is specified by a <literal>basetype</> parameter rather than being written next to the aggregate name. Note that this syntax allows - only one input parameter. To define an aggregate that does not examine - its input values, specify the <literal>basetype</> as + only one input parameter. To define a zero-argument aggregate function, + specify the <literal>basetype</> as <literal>"ANY"</> (not <literal>*</>). </para> </listitem> @@ -207,17 +209,15 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1; <listitem> <para> The name of the state transition function to be called for each - input data value. This is normally a function of two arguments, + input row. For an <replaceable class="PARAMETER">N</>-argument + aggregate function, the <replaceable class="PARAMETER">sfunc</> + must take <replaceable class="PARAMETER">N</>+1 arguments, the first being of type <replaceable - class="PARAMETER">state_data_type</replaceable> and the second - of type <replaceable - class="PARAMETER">input_data_type</replaceable>. Alternatively, - for an aggregate that does not examine its input values, the - function takes just one argument of type <replaceable - class="PARAMETER">state_data_type</replaceable>. In either case - the function must return a value of type <replaceable + class="PARAMETER">state_data_type</replaceable> and the rest + matching the declared input data type(s) of the aggregate. + The function must return a value of type <replaceable class="PARAMETER">state_data_type</replaceable>. This function - takes the current state value and the current input data item, + takes the current state value and the current input data value(s), and returns the next state value. </para> </listitem> @@ -237,7 +237,7 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1; <listitem> <para> The name of the final function called to compute the aggregate's - result after all input data has been traversed. The function + result after all input rows have been traversed. The function must take a single argument of type <replaceable class="PARAMETER">state_data_type</replaceable>. The return data type of the aggregate is defined as the return type of this @@ -269,7 +269,7 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1; <function>MAX</>-like aggregate. This is just an operator name (possibly schema-qualified). The operator is assumed to have the same input data types as - the aggregate. + the aggregate (which must be a single-argument aggregate). </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/drop_aggregate.sgml b/doc/src/sgml/ref/drop_aggregate.sgml index e6d03e0bdab..749c06525ae 100644 --- a/doc/src/sgml/ref/drop_aggregate.sgml +++ b/doc/src/sgml/ref/drop_aggregate.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/ref/drop_aggregate.sgml,v 1.28 2006/06/16 22:27:55 adunstan Exp $ +$PostgreSQL: pgsql/doc/src/sgml/ref/drop_aggregate.sgml,v 1.29 2006/07/27 19:52:04 tgl Exp $ PostgreSQL documentation --> @@ -20,7 +20,7 @@ PostgreSQL documentation <refsynopsisdiv> <synopsis> -DROP AGGREGATE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> ( <replaceable class="PARAMETER">type</replaceable> ) [ CASCADE | RESTRICT ] +DROP AGGREGATE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> ( <replaceable class="PARAMETER">type</replaceable> [ , ... ] ) [ CASCADE | RESTRICT ] </synopsis> </refsynopsisdiv> @@ -43,7 +43,7 @@ DROP AGGREGATE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> ( <term><literal>IF EXISTS</literal></term> <listitem> <para> - Do not throw an error if the aggregate does not exist. A notice is issued + Do not throw an error if the aggregate does not exist. A notice is issued in this case. </para> </listitem> @@ -62,8 +62,9 @@ DROP AGGREGATE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> ( <term><replaceable class="parameter">type</replaceable></term> <listitem> <para> - The argument data type of the aggregate function, or - <literal>*</literal> if the function accepts any data type. + An input data type on which the aggregate function operates. + To reference a zero-argument aggregate function, write <literal>*</> + in place of the list of input data types. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 74a53368463..97809bb7b19 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.165 2006/05/31 22:34:35 tgl Exp $ +$PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.166 2006/07/27 19:52:04 tgl Exp $ PostgreSQL documentation --> @@ -854,7 +854,7 @@ testdb=> <listitem> <para> Lists all available aggregate functions, together with the data - type they operate on. If <replaceable + types they operate on. If <replaceable class="parameter">pattern</replaceable> is specified, only aggregates whose names match the pattern are shown. </para> diff --git a/doc/src/sgml/sql.sgml b/doc/src/sgml/sql.sgml index b27400b4a45..d8b2934063d 100644 --- a/doc/src/sgml/sql.sgml +++ b/doc/src/sgml/sql.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/sql.sgml,v 1.40 2006/04/30 18:30:38 tgl Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/sql.sgml,v 1.41 2006/07/27 19:52:04 tgl Exp $ --> <chapter id="sql-intro"> <title>SQL</title> @@ -1247,13 +1247,13 @@ select sname, pname from supplier </sect3> <sect3> - <title id="aggregates-tutorial">Aggregate Operators</title> + <title id="aggregates-tutorial">Aggregate Functions</title> <para> - <acronym>SQL</acronym> provides aggregate operators (e.g. AVG, - COUNT, SUM, MIN, MAX) that take an expression as argument. The - expression is evaluated at each row that satisfies the WHERE - clause, and the aggregate operator is calculated over this set + <acronym>SQL</acronym> provides aggregate functions such as AVG, + COUNT, SUM, MIN, and MAX. The argument(s) of an aggregate function + are evaluated at each row that satisfies the WHERE + clause, and the aggregate function is calculated over this set of input values. Normally, an aggregate delivers a single result for a whole <command>SELECT</command> statement. But if grouping is specified in the query, then a separate calculation @@ -1311,10 +1311,10 @@ SELECT COUNT(PNO) <para> <acronym>SQL</acronym> allows one to partition the tuples of a table into groups. Then the - aggregate operators described above can be applied to the groups — - i.e. the value of the aggregate operator is no longer calculated over + aggregate functions described above can be applied to the groups — + i.e. the value of the aggregate function is no longer calculated over all the values of the specified column but over all values of a - group. Thus the aggregate operator is evaluated separately for every + group. Thus the aggregate function is evaluated separately for every group. </para> @@ -1396,7 +1396,7 @@ SELECT S.SNO, S.SNAME, COUNT(SE.PNO) <para> In our example we got four groups and now we can apply the aggregate - operator COUNT to every group leading to the final result of the query + function COUNT to every group leading to the final result of the query given above. </para> </example> @@ -1404,9 +1404,9 @@ SELECT S.SNO, S.SNAME, COUNT(SE.PNO) <para> Note that for a query using GROUP BY and aggregate - operators to make sense the target list can only refer directly to + functions to make sense, the target list can only refer directly to the attributes being grouped by. Other attributes may only be used - inside the argument of an aggregate function. Otherwise there would + inside the arguments of aggregate functions. Otherwise there would not be a unique value to associate with the other attributes. </para> diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml index 74cc813a681..58c9ddad6d5 100644 --- a/doc/src/sgml/syntax.sgml +++ b/doc/src/sgml/syntax.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/syntax.sgml,v 1.107 2006/06/26 17:24:40 tgl Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/syntax.sgml,v 1.108 2006/07/27 19:52:04 tgl Exp $ --> <chapter id="sql-syntax"> <title>SQL Syntax</title> @@ -673,8 +673,9 @@ CAST ( '<replaceable>string</replaceable>' AS <replaceable>type</replaceable> ) <para> The asterisk (<literal>*</literal>) is used in some contexts to denote all the fields of a table row or composite value. It also - has a special meaning when used as the argument of the - <function>COUNT</function> aggregate function. + has a special meaning when used as the argument of an + aggregate function, namely that the aggregate does not require + any explicit parameter. </para> </listitem> @@ -1269,9 +1270,9 @@ sqrt(2) syntax of an aggregate expression is one of the following: <synopsis> -<replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable>) -<replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable>) -<replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable>) +<replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] ) +<replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] ) +<replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] ) <replaceable>aggregate_name</replaceable> ( * ) </synopsis> @@ -1284,16 +1285,16 @@ sqrt(2) <para> The first form of aggregate expression invokes the aggregate - across all input rows for which the given expression yields a - non-null value. (Actually, it is up to the aggregate function + across all input rows for which the given expression(s) yield + non-null values. (Actually, it is up to the aggregate function whether to ignore null values or not — but all the standard ones do.) The second form is the same as the first, since <literal>ALL</literal> is the default. The third form invokes the - aggregate for all distinct non-null values of the expression found + aggregate for all distinct non-null values of the expressions found in the input rows. The last form invokes the aggregate once for each input row regardless of null or non-null values; since no particular input value is specified, it is generally only useful - for the <function>count()</function> aggregate function. + for the <function>count(*)</function> aggregate function. </para> <para> @@ -1323,7 +1324,7 @@ sqrt(2) <xref linkend="sql-syntax-scalar-subqueries"> and <xref linkend="functions-subquery">), the aggregate is normally evaluated over the rows of the subquery. But an exception occurs - if the aggregate's argument contains only outer-level variables: + if the aggregate's arguments contain only outer-level variables: the aggregate then belongs to the nearest such outer level, and is evaluated over the rows of that query. The aggregate expression as a whole is then an outer reference for the subquery it appears in, @@ -1332,6 +1333,13 @@ sqrt(2) appearing only in the result list or <literal>HAVING</> clause applies with respect to the query level that the aggregate belongs to. </para> + + <note> + <para> + <productname>PostgreSQL</productname> currently does not support + <literal>DISTINCT</> with more than one input expression. + </para> + </note> </sect2> <sect2 id="sql-syntax-type-casts"> diff --git a/doc/src/sgml/xaggr.sgml b/doc/src/sgml/xaggr.sgml index db6c077cbf6..c02f21048a7 100644 --- a/doc/src/sgml/xaggr.sgml +++ b/doc/src/sgml/xaggr.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/xaggr.sgml,v 1.31 2006/04/15 17:45:33 tgl Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/xaggr.sgml,v 1.32 2006/07/27 19:52:04 tgl Exp $ --> <sect1 id="xaggr"> <title>User-Defined Aggregates</title> @@ -10,11 +10,11 @@ <para> Aggregate functions in <productname>PostgreSQL</productname> - are expressed as <firstterm>state values</firstterm> + are expressed in terms of <firstterm>state values</firstterm> and <firstterm>state transition functions</firstterm>. - That is, an aggregate can be - defined in terms of state that is modified whenever an - input item is processed. To define a new aggregate + 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 @@ -85,13 +85,14 @@ SELECT sum(a) FROM test_complex; Another bit of default behavior for a <quote>strict</> transition function is that the previous state value is retained unchanged whenever a null input value is encountered. Thus, null values are ignored. If you - need some other behavior for null inputs, just do not define your transition - function as strict, and code it to test for null inputs and do - whatever is needed. + need some other behavior for null inputs, do not declare your + transition function as strict; instead code it to test for null inputs and + do whatever is needed. </para> <para> - <function>avg</> (average) is a more complex example of an aggregate. It requires + <function>avg</> (average) is a more complex example of an aggregate. + It requires two pieces of running state: the sum of the inputs and the count of the number of inputs. The final result is obtained by dividing these quantities. Average is typically implemented by using a @@ -117,7 +118,7 @@ CREATE AGGREGATE avg (float8) See <xref linkend="extend-types-polymorphic"> for an explanation of polymorphic functions. Going a step further, the aggregate function itself may be specified - with a polymorphic input type and state type, allowing a single + with polymorphic input type(s) and state type, allowing a single aggregate definition to serve for multiple input data types. Here is an example of a polymorphic aggregate: diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c index 47d21b35b01..747543e077d 100644 --- a/src/backend/catalog/pg_aggregate.c +++ b/src/backend/catalog/pg_aggregate.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/pg_aggregate.c,v 1.81 2006/07/14 14:52:17 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/pg_aggregate.c,v 1.82 2006/07/27 19:52:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -42,7 +42,8 @@ static Oid lookup_agg_function(List *fnName, int nargs, Oid *input_types, void AggregateCreate(const char *aggName, Oid aggNamespace, - Oid aggBaseType, + Oid *aggArgTypes, + int numArgs, List *aggtransfnName, List *aggfinalfnName, List *aggsortopName, @@ -57,9 +58,10 @@ AggregateCreate(const char *aggName, Oid transfn; Oid finalfn = InvalidOid; /* can be omitted */ Oid sortop = InvalidOid; /* can be omitted */ + bool hasPolyArg; Oid rettype; Oid finaltype; - Oid fnArgs[2]; /* we only deal with 1- and 2-arg fns */ + Oid *fnArgs; int nargs_transfn; Oid procOid; TupleDesc tupDesc; @@ -74,27 +76,34 @@ AggregateCreate(const char *aggName, if (!aggtransfnName) elog(ERROR, "aggregate must have a transition function"); + /* check for polymorphic arguments */ + hasPolyArg = false; + for (i = 0; i < numArgs; i++) + { + if (aggArgTypes[i] == ANYARRAYOID || + aggArgTypes[i] == ANYELEMENTOID) + { + hasPolyArg = true; + break; + } + } + /* - * If transtype is polymorphic, basetype must be polymorphic also; else we - * will have no way to deduce the actual transtype. + * If transtype is polymorphic, must have polymorphic argument also; + * else we will have no way to deduce the actual transtype. */ - if ((aggTransType == ANYARRAYOID || aggTransType == ANYELEMENTOID) && - !(aggBaseType == ANYARRAYOID || aggBaseType == ANYELEMENTOID)) + if (!hasPolyArg && + (aggTransType == ANYARRAYOID || aggTransType == ANYELEMENTOID)) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot determine transition data type"), - errdetail("An aggregate using \"anyarray\" or \"anyelement\" as " - "transition type must have one of them as its base type."))); + errdetail("An aggregate using \"anyarray\" or \"anyelement\" as transition type must have at least one argument of either type."))); - /* handle transfn */ + /* find the transfn */ + nargs_transfn = numArgs + 1; + fnArgs = (Oid *) palloc(nargs_transfn * sizeof(Oid)); fnArgs[0] = aggTransType; - if (aggBaseType == ANYOID) - nargs_transfn = 1; - else - { - fnArgs[1] = aggBaseType; - nargs_transfn = 2; - } + memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid)); transfn = lookup_agg_function(aggtransfnName, nargs_transfn, fnArgs, &rettype); @@ -123,13 +132,14 @@ AggregateCreate(const char *aggName, proc = (Form_pg_proc) GETSTRUCT(tup); /* - * If the transfn is strict and the initval is NULL, make sure input type - * and transtype are the same (or at least binary-compatible), so that + * 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 (!IsBinaryCoercible(aggBaseType, aggTransType)) + 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"))); @@ -153,32 +163,37 @@ AggregateCreate(const char *aggName, Assert(OidIsValid(finaltype)); /* - * If finaltype (i.e. aggregate return type) is polymorphic, basetype must + * If finaltype (i.e. aggregate return type) is polymorphic, inputs must * be polymorphic also, else parser will fail to deduce result type. - * (Note: given the previous test on transtype and basetype, this cannot + * (Note: given the previous test on transtype and inputs, this cannot * happen, unless someone has snuck a finalfn definition into the catalogs * that itself violates the rule against polymorphic result with no * polymorphic input.) */ - if ((finaltype == ANYARRAYOID || finaltype == ANYELEMENTOID) && - !(aggBaseType == ANYARRAYOID || aggBaseType == ANYELEMENTOID)) + if (!hasPolyArg && + (finaltype == ANYARRAYOID || finaltype == ANYELEMENTOID)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("cannot determine result data type"), errdetail("An aggregate returning \"anyarray\" or \"anyelement\" " - "must have one of them as its base type."))); + "must have at least one argument of either type."))); /* handle sortop, if supplied */ if (aggsortopName) + { + if (numArgs != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("sort operator can only be specified for single-argument aggregates"))); sortop = LookupOperName(NULL, aggsortopName, - aggBaseType, aggBaseType, + aggArgTypes[0], aggArgTypes[0], false, -1); + } /* * Everything looks okay. Try to create the pg_proc entry for the * aggregate. (This could fail if there's already a conflicting entry.) */ - fnArgs[0] = aggBaseType; procOid = ProcedureCreate(aggName, aggNamespace, @@ -195,7 +210,8 @@ AggregateCreate(const char *aggName, false, /* isStrict (not needed for agg) */ PROVOLATILE_IMMUTABLE, /* volatility (not * needed for agg) */ - buildoidvector(fnArgs, 1), /* paramTypes */ + buildoidvector(aggArgTypes, + numArgs), /* paramTypes */ PointerGetDatum(NULL), /* allParamTypes */ PointerGetDatum(NULL), /* parameterModes */ PointerGetDatum(NULL)); /* parameterNames */ @@ -279,6 +295,8 @@ lookup_agg_function(List *fnName, Oid *true_oid_array; FuncDetailCode fdresult; AclResult aclresult; + int i; + bool allPolyArgs = true; /* * func_get_detail looks up the function in the catalogs, does @@ -307,13 +325,17 @@ lookup_agg_function(List *fnName, * If the given type(s) are all polymorphic, there's nothing we can check. * Otherwise, enforce consistency, and possibly refine the result type. */ - if ((input_types[0] == ANYARRAYOID || input_types[0] == ANYELEMENTOID) && - (nargs == 1 || - (input_types[1] == ANYARRAYOID || input_types[1] == ANYELEMENTOID))) + for (i = 0; i < nargs; i++) { - /* nothing to check here */ + if (input_types[i] != ANYARRAYOID && + input_types[i] != ANYELEMENTOID) + { + allPolyArgs = false; + break; + } } - else + + if (!allPolyArgs) { *rettype = enforce_generic_type_consistency(input_types, true_oid_array, @@ -325,22 +347,16 @@ lookup_agg_function(List *fnName, * func_get_detail will find functions requiring run-time argument type * coercion, but nodeAgg.c isn't prepared to deal with that */ - if (true_oid_array[0] != ANYARRAYOID && - true_oid_array[0] != ANYELEMENTOID && - !IsBinaryCoercible(input_types[0], true_oid_array[0])) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("function %s requires run-time type coercion", - func_signature_string(fnName, nargs, true_oid_array)))); - - if (nargs == 2 && - true_oid_array[1] != ANYARRAYOID && - true_oid_array[1] != ANYELEMENTOID && - !IsBinaryCoercible(input_types[1], true_oid_array[1])) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("function %s requires run-time type coercion", - func_signature_string(fnName, nargs, true_oid_array)))); + for (i = 0; i < nargs; i++) + { + if (true_oid_array[i] != ANYARRAYOID && + true_oid_array[i] != ANYELEMENTOID && + !IsBinaryCoercible(input_types[i], true_oid_array[i])) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("function %s requires run-time type coercion", + func_signature_string(fnName, nargs, true_oid_array)))); + } /* Check aggregate creator has permission to call the function */ aclresult = pg_proc_aclcheck(fnOid, GetUserId(), ACL_EXECUTE); diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c index 40b4d59cb9c..cb4dfee77e2 100644 --- a/src/backend/commands/aggregatecmds.c +++ b/src/backend/commands/aggregatecmds.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/aggregatecmds.c,v 1.37 2006/07/14 14:52:18 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/aggregatecmds.c,v 1.38 2006/07/27 19:52:04 tgl Exp $ * * DESCRIPTION * The "DefineFoo" routines take the parse tree and pick out the @@ -57,7 +57,8 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters) TypeName *baseType = NULL; TypeName *transType = NULL; char *initval = NULL; - Oid baseTypeId; + Oid *aggArgTypes; + int numArgs; Oid transTypeId; ListCell *pl; @@ -116,12 +117,13 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters) errmsg("aggregate sfunc must be specified"))); /* - * look up the aggregate's input datatype. + * look up the aggregate's input datatype(s). */ if (oldstyle) { /* - * Old style: use basetype parameter. This supports only one input. + * Old style: use basetype parameter. This supports aggregates + * of zero or one input, with input type ANY meaning zero inputs. * * Historically we allowed the command to look like basetype = 'ANY' * so we must do a case-insensitive comparison for the name ANY. Ugh. @@ -132,37 +134,37 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters) errmsg("aggregate input type must be specified"))); if (pg_strcasecmp(TypeNameToString(baseType), "ANY") == 0) - baseTypeId = ANYOID; + { + numArgs = 0; + aggArgTypes = NULL; + } else - baseTypeId = typenameTypeId(NULL, baseType); + { + numArgs = 1; + aggArgTypes = (Oid *) palloc(sizeof(Oid)); + aggArgTypes[0] = typenameTypeId(NULL, baseType); + } } else { /* - * New style: args is a list of TypeNames. For the moment, though, - * we allow at most one. + * New style: args is a list of TypeNames (possibly zero of 'em). */ + ListCell *lc; + int i = 0; + if (baseType != NULL) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("basetype is redundant with aggregate input type specification"))); - if (args == NIL) - { - /* special case for agg(*) */ - baseTypeId = ANYOID; - } - else if (list_length(args) != 1) + numArgs = list_length(args); + aggArgTypes = (Oid *) palloc(sizeof(Oid) * numArgs); + foreach(lc, args) { - /* temporarily reject > 1 arg */ - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("aggregates can have only one input"))); - baseTypeId = InvalidOid; /* keep compiler quiet */ - } - else - { - baseTypeId = typenameTypeId(NULL, (TypeName *) linitial(args)); + TypeName *curTypeName = (TypeName *) lfirst(lc); + + aggArgTypes[i++] = typenameTypeId(NULL, curTypeName); } } @@ -187,7 +189,8 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters) */ AggregateCreate(aggName, /* aggregate name */ aggNamespace, /* namespace */ - baseTypeId, /* type of data being aggregated */ + aggArgTypes, /* input data type(s) */ + numArgs, transfuncName, /* step function name */ finalfuncName, /* final function name */ sortoperatorName, /* sort operator name */ @@ -211,7 +214,7 @@ RemoveAggregate(RemoveFuncStmt *stmt) /* Look up function and make sure it's an aggregate */ procOid = LookupAggNameTypeNames(aggName, aggArgs, stmt->missing_ok); - + if (!OidIsValid(procOid)) { /* we only get here if stmt->missing_ok is true */ diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index fc80472adee..7f341940d67 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.192 2006/07/14 14:52:19 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.193 2006/07/27 19:52:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -3174,10 +3174,11 @@ ExecInitExpr(Expr *node, PlanState *parent) aggstate->aggs = lcons(astate, aggstate->aggs); naggs = ++aggstate->numaggs; - astate->target = ExecInitExpr(aggref->target, parent); + astate->args = (List *) ExecInitExpr((Expr *) aggref->args, + parent); /* - * Complain if the aggregate's argument contains any + * Complain if the aggregate's arguments contain any * aggregates; nested agg functions are semantically * nonsensical. (This should have been caught earlier, * but we defend against it here anyway.) diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index 851a360d2f1..19410997b2c 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -6,8 +6,8 @@ * ExecAgg evaluates each aggregate in the following steps: * * transvalue = initcond - * foreach input_value do - * transvalue = transfunc(transvalue, input_value) + * foreach input_tuple do + * transvalue = transfunc(transvalue, input_value(s)) * result = finalfunc(transvalue) * * If a finalfunc is not supplied then the result is just the ending @@ -16,12 +16,12 @@ * If transfunc is marked "strict" in pg_proc and initcond is NULL, * then the first non-NULL input_value is assigned directly to transvalue, * and transfunc isn't applied until the second non-NULL input_value. - * The agg's input type and transtype must be the same in this case! + * The agg's first input type and transtype must be the same in this case! * * If transfunc is marked "strict" then NULL input_values are skipped, * keeping the previous transvalue. If transfunc is not strict then it * is called for every input tuple and must deal with NULL initcond - * or NULL input_value for itself. + * or NULL input_values for itself. * * If finalfunc is marked "strict" then it is not called when the * ending transvalue is NULL, instead a NULL result is created @@ -61,7 +61,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/nodeAgg.c,v 1.144 2006/07/14 14:52:19 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/executor/nodeAgg.c,v 1.145 2006/07/27 19:52:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -103,6 +103,9 @@ typedef struct AggStatePerAggData AggrefExprState *aggrefstate; Aggref *aggref; + /* number of input arguments for aggregate */ + int numArguments; + /* Oids of transfer functions */ Oid transfn_oid; Oid finalfn_oid; /* may be InvalidOid */ @@ -214,7 +217,7 @@ static void initialize_aggregates(AggState *aggstate, static void advance_transition_function(AggState *aggstate, AggStatePerAgg peraggstate, AggStatePerGroup pergroupstate, - Datum newVal, bool isNull); + FunctionCallInfoData *fcinfo); static void advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup); static void process_sorted_aggregate(AggState *aggstate, AggStatePerAgg peraggstate, @@ -314,7 +317,11 @@ initialize_aggregates(AggState *aggstate, } /* - * Given a new input value, advance the transition function of an aggregate. + * 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. */ @@ -322,19 +329,24 @@ static void advance_transition_function(AggState *aggstate, AggStatePerAgg peraggstate, AggStatePerGroup pergroupstate, - Datum newVal, bool isNull) + FunctionCallInfoData *fcinfo) { - FunctionCallInfoData fcinfo; + int numArguments = peraggstate->numArguments; MemoryContext oldContext; + Datum newVal; + int i; if (peraggstate->transfn.fn_strict) { /* - * For a strict transfn, nothing happens at a NULL input tuple; we - * just keep the prior transValue. + * For a strict transfn, nothing happens when there's a NULL input; + * we just keep the prior transValue. */ - if (isNull) - return; + for (i = 1; i <= numArguments; i++) + { + if (fcinfo->argnull[i]) + return; + } if (pergroupstate->noTransValue) { /* @@ -347,7 +359,7 @@ advance_transition_function(AggState *aggstate, * do not need to pfree the old transValue, since it's NULL. */ oldContext = MemoryContextSwitchTo(aggstate->aggcontext); - pergroupstate->transValue = datumCopy(newVal, + pergroupstate->transValue = datumCopy(fcinfo->arg[1], peraggstate->transtypeByVal, peraggstate->transtypeLen); pergroupstate->transValueIsNull = false; @@ -373,14 +385,13 @@ advance_transition_function(AggState *aggstate, /* * OK to call the transition function */ - InitFunctionCallInfoData(fcinfo, &(peraggstate->transfn), 2, + InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn), + numArguments + 1, (void *) aggstate, NULL); - fcinfo.arg[0] = pergroupstate->transValue; - fcinfo.argnull[0] = pergroupstate->transValueIsNull; - fcinfo.arg[1] = newVal; - fcinfo.argnull[1] = isNull; + fcinfo->arg[0] = pergroupstate->transValue; + fcinfo->argnull[0] = pergroupstate->transValueIsNull; - newVal = FunctionCallInvoke(&fcinfo); + newVal = FunctionCallInvoke(fcinfo); /* * If pass-by-ref datatype, must copy the new value into aggcontext and @@ -390,7 +401,7 @@ advance_transition_function(AggState *aggstate, if (!peraggstate->transtypeByVal && DatumGetPointer(newVal) != DatumGetPointer(pergroupstate->transValue)) { - if (!fcinfo.isnull) + if (!fcinfo->isnull) { MemoryContextSwitchTo(aggstate->aggcontext); newVal = datumCopy(newVal, @@ -402,7 +413,7 @@ advance_transition_function(AggState *aggstate, } pergroupstate->transValue = newVal; - pergroupstate->transValueIsNull = fcinfo.isnull; + pergroupstate->transValueIsNull = fcinfo->isnull; MemoryContextSwitchTo(oldContext); } @@ -423,27 +434,46 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup) for (aggno = 0; aggno < aggstate->numaggs; aggno++) { - AggStatePerAgg peraggstate = &aggstate->peragg[aggno]; - AggStatePerGroup pergroupstate = &pergroup[aggno]; - AggrefExprState *aggrefstate = peraggstate->aggrefstate; - Aggref *aggref = peraggstate->aggref; - Datum newVal; - bool isNull; + AggStatePerAgg peraggstate = &aggstate->peragg[aggno]; + AggStatePerGroup pergroupstate = &pergroup[aggno]; + AggrefExprState *aggrefstate = peraggstate->aggrefstate; + Aggref *aggref = peraggstate->aggref; + FunctionCallInfoData fcinfo; + int i; + ListCell *arg; + MemoryContext oldContext; - newVal = ExecEvalExprSwitchContext(aggrefstate->target, econtext, - &isNull, NULL); + /* Switch memory context just once for all args */ + oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + + /* Evaluate inputs and save in fcinfo */ + /* We start from 1, since the 0th arg will be the transition value */ + i = 1; + foreach(arg, aggrefstate->args) + { + ExprState *argstate = (ExprState *) lfirst(arg); + + fcinfo.arg[i] = ExecEvalExpr(argstate, econtext, + fcinfo.argnull + i, NULL); + i++; + } + + /* Switch back */ + MemoryContextSwitchTo(oldContext); if (aggref->aggdistinct) { /* in DISTINCT mode, we may ignore nulls */ - if (isNull) + /* XXX we assume there is only one input column */ + if (fcinfo.argnull[1]) continue; - tuplesort_putdatum(peraggstate->sortstate, newVal, isNull); + tuplesort_putdatum(peraggstate->sortstate, fcinfo.arg[1], + fcinfo.argnull[1]); } else { advance_transition_function(aggstate, peraggstate, pergroupstate, - newVal, isNull); + &fcinfo); } } } @@ -465,11 +495,15 @@ process_sorted_aggregate(AggState *aggstate, bool haveOldVal = false; MemoryContext workcontext = aggstate->tmpcontext->ecxt_per_tuple_memory; MemoryContext oldContext; - Datum newVal; - bool isNull; + Datum *newVal; + bool *isNull; + FunctionCallInfoData fcinfo; tuplesort_performsort(peraggstate->sortstate); + newVal = fcinfo.arg + 1; + isNull = fcinfo.argnull + 1; + /* * Note: if input type is pass-by-ref, the datums returned by the sort are * freshly palloc'd in the per-query context, so we must be careful to @@ -477,13 +511,13 @@ process_sorted_aggregate(AggState *aggstate, */ while (tuplesort_getdatum(peraggstate->sortstate, true, - &newVal, &isNull)) + newVal, isNull)) { /* * DISTINCT always suppresses nulls, per SQL spec, regardless of the * transition function's strictness. */ - if (isNull) + if (*isNull) continue; /* @@ -495,21 +529,21 @@ process_sorted_aggregate(AggState *aggstate, if (haveOldVal && DatumGetBool(FunctionCall2(&peraggstate->equalfn, - oldVal, newVal))) + oldVal, *newVal))) { /* equal to prior, so forget this one */ if (!peraggstate->inputtypeByVal) - pfree(DatumGetPointer(newVal)); + pfree(DatumGetPointer(*newVal)); } else { advance_transition_function(aggstate, peraggstate, pergroupstate, - newVal, false); + &fcinfo); /* forget the old value, if any */ if (haveOldVal && !peraggstate->inputtypeByVal) pfree(DatumGetPointer(oldVal)); /* and remember the new one for subsequent equality checks */ - oldVal = newVal; + oldVal = *newVal; haveOldVal = true; } @@ -1286,7 +1320,8 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) AggrefExprState *aggrefstate = (AggrefExprState *) lfirst(l); Aggref *aggref = (Aggref *) aggrefstate->xprstate.expr; AggStatePerAgg peraggstate; - Oid inputType; + Oid inputTypes[FUNC_MAX_ARGS]; + int numArguments; HeapTuple aggTuple; Form_pg_aggregate aggform; Oid aggtranstype; @@ -1297,6 +1332,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) *finalfnexpr; Datum textInitVal; int i; + ListCell *lc; /* Planner should have assigned aggregate to correct level */ Assert(aggref->agglevelsup == 0); @@ -1324,13 +1360,19 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) /* Fill in the peraggstate data */ peraggstate->aggrefstate = aggrefstate; peraggstate->aggref = aggref; + numArguments = list_length(aggref->args); + peraggstate->numArguments = numArguments; /* - * Get actual datatype of the input. We need this because it may be - * different from the agg's declared input type, when the agg accepts - * ANY (eg, COUNT(*)) or ANYARRAY or ANYELEMENT. + * Get actual datatypes of the inputs. These could be different + * from the agg's declared input types, when the agg accepts ANY, + * ANYARRAY or ANYELEMENT. */ - inputType = exprType((Node *) aggref->target); + i = 0; + foreach(lc, aggref->args) + { + inputTypes[i++] = exprType((Node *) lfirst(lc)); + } aggTuple = SearchSysCache(AGGFNOID, ObjectIdGetDatum(aggref->aggfnoid), @@ -1383,21 +1425,23 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) aggtranstype = aggform->aggtranstype; if (aggtranstype == ANYARRAYOID || aggtranstype == ANYELEMENTOID) { - /* have to fetch the agg's declared input type... */ - Oid *agg_arg_types; + /* have to fetch the agg's declared input types... */ + Oid *declaredArgTypes; int agg_nargs; (void) get_func_signature(aggref->aggfnoid, - &agg_arg_types, &agg_nargs); - Assert(agg_nargs == 1); - aggtranstype = resolve_generic_type(aggtranstype, - inputType, - agg_arg_types[0]); - pfree(agg_arg_types); + &declaredArgTypes, &agg_nargs); + Assert(agg_nargs == numArguments); + aggtranstype = enforce_generic_type_consistency(inputTypes, + declaredArgTypes, + agg_nargs, + aggtranstype); + pfree(declaredArgTypes); } /* build expression trees using actual argument & result types */ - build_aggregate_fnexprs(inputType, + build_aggregate_fnexprs(inputTypes, + numArguments, aggtranstype, aggref->aggtype, transfn_oid, @@ -1437,14 +1481,15 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) /* * If the transfn is strict and the initval is NULL, make sure input - * type and transtype are the same (or at least binary- compatible), + * 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. This should have been checked at agg definition time, * but just in case... */ if (peraggstate->transfn.fn_strict && peraggstate->initValueIsNull) { - if (!IsBinaryCoercible(inputType, aggtranstype)) + if (numArguments < 1 || + !IsBinaryCoercible(inputTypes[0], aggtranstype)) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("aggregate %u needs to have compatible input type and transition type", @@ -1458,14 +1503,25 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) /* We don't implement DISTINCT aggs in the HASHED case */ Assert(node->aggstrategy != AGG_HASHED); - peraggstate->inputType = inputType; - get_typlenbyval(inputType, + /* + * We don't currently implement DISTINCT aggs for aggs having + * more than one argument. This isn't required for anything + * in the SQL spec, but really it ought to be implemented for + * feature-completeness. FIXME someday. + */ + if (numArguments != 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("DISTINCT is supported only for single-argument aggregates"))); + + peraggstate->inputType = inputTypes[0]; + get_typlenbyval(inputTypes[0], &peraggstate->inputtypeLen, &peraggstate->inputtypeByVal); - eq_function = equality_oper_funcid(inputType); + eq_function = equality_oper_funcid(inputTypes[0]); fmgr_info(eq_function, &(peraggstate->equalfn)); - peraggstate->sortOperator = ordering_oper_opid(inputType); + peraggstate->sortOperator = ordering_oper_opid(inputTypes[0]); peraggstate->sortstate = NULL; } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index a18f118bce4..f2b2afd81af 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.343 2006/07/14 14:52:19 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.344 2006/07/27 19:52:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -743,7 +743,7 @@ _copyAggref(Aggref *from) COPY_SCALAR_FIELD(aggfnoid); COPY_SCALAR_FIELD(aggtype); - COPY_NODE_FIELD(target); + COPY_NODE_FIELD(args); COPY_SCALAR_FIELD(agglevelsup); COPY_SCALAR_FIELD(aggstar); COPY_SCALAR_FIELD(aggdistinct); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index a1b758e7495..0122ebd629c 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.277 2006/07/14 14:52:20 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.278 2006/07/27 19:52:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -156,7 +156,7 @@ _equalAggref(Aggref *a, Aggref *b) { COMPARE_SCALAR_FIELD(aggfnoid); COMPARE_SCALAR_FIELD(aggtype); - COMPARE_NODE_FIELD(target); + COMPARE_NODE_FIELD(args); COMPARE_SCALAR_FIELD(agglevelsup); COMPARE_SCALAR_FIELD(aggstar); COMPARE_SCALAR_FIELD(aggdistinct); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 1b85b9a8063..61d49572ca8 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.278 2006/07/14 14:52:20 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.279 2006/07/27 19:52:05 tgl Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -635,7 +635,7 @@ _outAggref(StringInfo str, Aggref *node) WRITE_OID_FIELD(aggfnoid); WRITE_OID_FIELD(aggtype); - WRITE_NODE_FIELD(target); + WRITE_NODE_FIELD(args); WRITE_UINT_FIELD(agglevelsup); WRITE_BOOL_FIELD(aggstar); WRITE_BOOL_FIELD(aggdistinct); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 883155dcd07..265a5b369ee 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.191 2006/07/03 22:45:39 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.192 2006/07/27 19:52:05 tgl Exp $ * * NOTES * Path and Plan nodes do not have any readfuncs support, because we @@ -348,7 +348,7 @@ _readAggref(void) READ_OID_FIELD(aggfnoid); READ_OID_FIELD(aggtype); - READ_NODE_FIELD(target); + READ_NODE_FIELD(args); READ_UINT_FIELD(agglevelsup); READ_BOOL_FIELD(aggstar); READ_BOOL_FIELD(aggdistinct); diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c index 39e77897c0a..849b81a9a75 100644 --- a/src/backend/optimizer/plan/planagg.c +++ b/src/backend/optimizer/plan/planagg.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.19 2006/07/26 19:31:50 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.20 2006/07/27 19:52:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -217,12 +217,13 @@ find_minmax_aggs_walker(Node *node, List **context) { Aggref *aggref = (Aggref *) node; Oid aggsortop; + Expr *curTarget; MinMaxAggInfo *info; ListCell *l; Assert(aggref->agglevelsup == 0); - if (aggref->aggstar) - return true; /* foo(*) is surely not optimizable */ + if (list_length(aggref->args) != 1) + return true; /* it couldn't be MIN/MAX */ /* note: we do not care if DISTINCT is mentioned ... */ aggsortop = fetch_agg_sort_op(aggref->aggfnoid); @@ -232,18 +233,19 @@ find_minmax_aggs_walker(Node *node, List **context) /* * Check whether it's already in the list, and add it if not. */ + curTarget = linitial(aggref->args); foreach(l, *context) { info = (MinMaxAggInfo *) lfirst(l); if (info->aggfnoid == aggref->aggfnoid && - equal(info->target, aggref->target)) + equal(info->target, curTarget)) return false; } info = (MinMaxAggInfo *) palloc0(sizeof(MinMaxAggInfo)); info->aggfnoid = aggref->aggfnoid; info->aggsortop = aggsortop; - info->target = aggref->target; + info->target = curTarget; *context = lappend(*context, info); @@ -520,13 +522,14 @@ replace_aggs_with_params_mutator(Node *node, List **context) { Aggref *aggref = (Aggref *) node; ListCell *l; + Expr *curTarget = linitial(aggref->args); foreach(l, *context) { MinMaxAggInfo *info = (MinMaxAggInfo *) lfirst(l); if (info->aggfnoid == aggref->aggfnoid && - equal(info->target, aggref->target)) + equal(info->target, curTarget)) return (Node *) info->param; } elog(ERROR, "failed to re-find aggregate info record"); diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 0196e4b3ff0..dfc43149d3a 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.214 2006/07/14 14:52:21 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.215 2006/07/27 19:52:05 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -397,17 +397,27 @@ count_agg_clauses_walker(Node *node, AggClauseCounts *counts) if (IsA(node, Aggref)) { Aggref *aggref = (Aggref *) node; - Oid inputType; + Oid *inputTypes; + int numArguments; HeapTuple aggTuple; Form_pg_aggregate aggform; Oid aggtranstype; + int i; + ListCell *l; Assert(aggref->agglevelsup == 0); counts->numAggs++; if (aggref->aggdistinct) counts->numDistinctAggs++; - inputType = exprType((Node *) aggref->target); + /* extract argument types */ + numArguments = list_length(aggref->args); + inputTypes = (Oid *) palloc(sizeof(Oid) * numArguments); + i = 0; + foreach(l, aggref->args) + { + inputTypes[i++] = exprType((Node *) lfirst(l)); + } /* fetch aggregate transition datatype from pg_aggregate */ aggTuple = SearchSysCache(AGGFNOID, @@ -423,17 +433,18 @@ count_agg_clauses_walker(Node *node, AggClauseCounts *counts) /* resolve actual type of transition state, if polymorphic */ if (aggtranstype == ANYARRAYOID || aggtranstype == ANYELEMENTOID) { - /* have to fetch the agg's declared input type... */ - Oid *agg_arg_types; + /* have to fetch the agg's declared input types... */ + Oid *declaredArgTypes; int agg_nargs; (void) get_func_signature(aggref->aggfnoid, - &agg_arg_types, &agg_nargs); - Assert(agg_nargs == 1); - aggtranstype = resolve_generic_type(aggtranstype, - inputType, - agg_arg_types[0]); - pfree(agg_arg_types); + &declaredArgTypes, &agg_nargs); + Assert(agg_nargs == numArguments); + aggtranstype = enforce_generic_type_consistency(inputTypes, + declaredArgTypes, + agg_nargs, + aggtranstype); + pfree(declaredArgTypes); } /* @@ -448,12 +459,12 @@ count_agg_clauses_walker(Node *node, AggClauseCounts *counts) int32 avgwidth; /* - * If transition state is of same type as 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 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 (aggtranstype == inputType) - aggtranstypmod = exprTypmod((Node *) aggref->target); + if (numArguments > 0 && aggtranstype == inputTypes[0]) + aggtranstypmod = exprTypmod((Node *) linitial(aggref->args)); else aggtranstypmod = -1; @@ -464,10 +475,10 @@ count_agg_clauses_walker(Node *node, AggClauseCounts *counts) } /* - * Complain if the aggregate's argument contains any aggregates; + * Complain if the aggregate's arguments contain any aggregates; * nested agg functions are semantically nonsensical. */ - if (contain_agg_clause((Node *) aggref->target)) + if (contain_agg_clause((Node *) aggref->args)) ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR), errmsg("aggregate function calls may not be nested"))); @@ -3026,7 +3037,14 @@ expression_tree_walker(Node *node, /* primitive node types with no expression subnodes */ break; case T_Aggref: - return walker(((Aggref *) node)->target, context); + { + Aggref *expr = (Aggref *) node; + + if (expression_tree_walker((Node *) expr->args, + walker, context)) + return true; + } + break; case T_ArrayRef: { ArrayRef *aref = (ArrayRef *) node; @@ -3448,7 +3466,7 @@ expression_tree_mutator(Node *node, Aggref *newnode; FLATCOPY(newnode, aggref, Aggref); - MUTATE(newnode->target, aggref->target, Expr *); + MUTATE(newnode->args, aggref->args, List *); return (Node *) newnode; } break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 754777c57bc..afd88e8125b 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.551 2006/07/03 22:45:39 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.552 2006/07/27 19:52:05 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -7346,10 +7346,8 @@ func_expr: func_name '(' ')' | func_name '(' '*' ')' { /* - * For now, we transform AGGREGATE(*) into AGGREGATE(1). - * - * This does the right thing for COUNT(*) (in fact, - * any certainly-non-null expression would do for COUNT), + * We consider AGGREGATE(*) to invoke a parameterless + * aggregate. This does the right thing for COUNT(*), * and there are no other aggregates in SQL92 that accept * '*' as parameter. * @@ -7358,12 +7356,8 @@ func_expr: func_name '(' ')' * really was. */ FuncCall *n = makeNode(FuncCall); - A_Const *star = makeNode(A_Const); - - star->val.type = T_Integer; - star->val.val.ival = 1; n->funcname = $1; - n->args = list_make1(star); + n->args = NIL; n->agg_star = TRUE; n->agg_distinct = FALSE; n->location = @1; diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 6d381cd2d9e..3bda907c994 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_agg.c,v 1.72 2006/07/14 14:52:21 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_agg.c,v 1.73 2006/07/27 19:52:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -55,10 +55,10 @@ transformAggregateCall(ParseState *pstate, Aggref *agg) /* * The aggregate's level is the same as the level of the lowest-level - * variable or aggregate in its argument; or if it contains no variables + * variable or aggregate in its arguments; or if it contains no variables * at all, we presume it to be local. */ - min_varlevel = find_minimum_var_level((Node *) agg->target); + min_varlevel = find_minimum_var_level((Node *) agg->args); /* * An aggregate can't directly contain another aggregate call of the same @@ -67,7 +67,7 @@ transformAggregateCall(ParseState *pstate, Aggref *agg) */ if (min_varlevel == 0) { - if (checkExprHasAggs((Node *) agg->target)) + if (checkExprHasAggs((Node *) agg->args)) ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR), errmsg("aggregate function calls may not be nested"))); @@ -360,7 +360,7 @@ check_ungrouped_columns_walker(Node *node, * (The trees will never actually be executed, however, so we can skimp * a bit on correctness.) * - * agg_input_type, agg_state_type, agg_result_type identify the input, + * agg_input_types, agg_state_type, agg_result_type identify the input, * transition, and result types of the aggregate. These should all be * resolved to actual types (ie, none should ever be ANYARRAY or ANYELEMENT). * @@ -371,7 +371,8 @@ check_ungrouped_columns_walker(Node *node, * *finalfnexpr. The latter is set to NULL if there's no finalfn. */ void -build_aggregate_fnexprs(Oid agg_input_type, +build_aggregate_fnexprs(Oid *agg_input_types, + int agg_num_inputs, Oid agg_state_type, Oid agg_result_type, Oid transfn_oid, @@ -379,13 +380,9 @@ build_aggregate_fnexprs(Oid agg_input_type, Expr **transfnexpr, Expr **finalfnexpr) { - int transfn_nargs; - Param *arg0; - Param *arg1; + Param *argp; List *args; - - /* get the transition function arg count */ - transfn_nargs = get_func_nargs(transfn_oid); + int i; /* * Build arg list to use in the transfn FuncExpr node. We really only care @@ -393,22 +390,21 @@ build_aggregate_fnexprs(Oid agg_input_type, * get_fn_expr_argtype(), so it's okay to use Param nodes that don't * correspond to any real Param. */ - arg0 = makeNode(Param); - arg0->paramkind = PARAM_EXEC; - arg0->paramid = -1; - arg0->paramtype = agg_state_type; + argp = makeNode(Param); + argp->paramkind = PARAM_EXEC; + argp->paramid = -1; + argp->paramtype = agg_state_type; - if (transfn_nargs == 2) - { - arg1 = makeNode(Param); - arg1->paramkind = PARAM_EXEC; - arg1->paramid = -1; - arg1->paramtype = agg_input_type; + args = list_make1(argp); - args = list_make2(arg0, arg1); + for (i = 0; i < agg_num_inputs; i++) + { + argp = makeNode(Param); + argp->paramkind = PARAM_EXEC; + argp->paramid = -1; + argp->paramtype = agg_input_types[i]; + args = lappend(args, argp); } - else - args = list_make1(arg0); *transfnexpr = (Expr *) makeFuncExpr(transfn_oid, agg_state_type, @@ -425,11 +421,11 @@ build_aggregate_fnexprs(Oid agg_input_type, /* * Build expr tree for final function */ - arg0 = makeNode(Param); - arg0->paramkind = PARAM_EXEC; - arg0->paramid = -1; - arg0->paramtype = agg_state_type; - args = list_make1(arg0); + argp = makeNode(Param); + argp->paramkind = PARAM_EXEC; + argp->paramid = -1; + argp->paramtype = agg_state_type; + args = list_make1(argp); *finalfnexpr = (Expr *) makeFuncExpr(finalfn_oid, agg_result_type, diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index aa0632a3898..b1b53164f80 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.188 2006/07/14 14:52:22 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.189 2006/07/27 19:52:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -259,10 +259,21 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, aggref->aggfnoid = funcid; aggref->aggtype = rettype; - aggref->target = linitial(fargs); + aggref->args = fargs; aggref->aggstar = agg_star; aggref->aggdistinct = agg_distinct; + /* + * 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", + NameListToString(funcname)), + parser_errposition(pstate, location))); + /* parse_agg.c does additional aggregate-specific processing */ transformAggregateCall(pstate, aggref); @@ -1194,9 +1205,7 @@ LookupFuncNameTypeNames(List *funcname, List *argtypes, bool noError) * * This is almost like LookupFuncNameTypeNames, but the error messages refer * to aggregates rather than plain functions, and we verify that the found - * function really is an aggregate, and we recognize the convention used by - * the grammar that agg(*) translates to a NIL list, which we have to treat - * as one ANY argument. (XXX this ought to be changed) + * function really is an aggregate. */ Oid LookupAggNameTypeNames(List *aggname, List *argtypes, bool noError) @@ -1204,7 +1213,7 @@ LookupAggNameTypeNames(List *aggname, List *argtypes, bool noError) Oid argoids[FUNC_MAX_ARGS]; int argcount; int i; - ListCell *args_item; + ListCell *lc; Oid oid; HeapTuple ftup; Form_pg_proc pform; @@ -1216,29 +1225,18 @@ LookupAggNameTypeNames(List *aggname, List *argtypes, bool noError) errmsg("functions cannot have more than %d arguments", FUNC_MAX_ARGS))); - if (argcount == 0) - { - /* special case for agg(*) */ - argoids[0] = ANYOID; - argcount = 1; - } - else + i = 0; + foreach(lc, argtypes) { - args_item = list_head(argtypes); - for (i = 0; i < argcount; i++) - { - TypeName *t = (TypeName *) lfirst(args_item); - - argoids[i] = LookupTypeName(NULL, t); - - if (!OidIsValid(argoids[i])) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("type \"%s\" does not exist", - TypeNameToString(t)))); + TypeName *t = (TypeName *) lfirst(lc); - args_item = lnext(args_item); - } + argoids[i] = LookupTypeName(NULL, t); + if (!OidIsValid(argoids[i])) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("type \"%s\" does not exist", + TypeNameToString(t)))); + i++; } oid = LookupFuncName(aggname, argcount, argoids, true); @@ -1247,7 +1245,7 @@ LookupAggNameTypeNames(List *aggname, List *argtypes, bool noError) { if (noError) return InvalidOid; - if (argcount == 1 && argoids[0] == ANYOID) + if (argcount == 0) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("aggregate %s(*) does not exist", diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 605efc60403..a2254b6e481 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -2,7 +2,7 @@ * ruleutils.c - Functions to convert stored expressions/querytrees * back to source text * - * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.228 2006/07/14 14:52:24 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.229 2006/07/27 19:52:06 tgl Exp $ **********************************************************************/ #include "postgres.h" @@ -3880,15 +3880,29 @@ static void get_agg_expr(Aggref *aggref, deparse_context *context) { StringInfo buf = context->buf; - Oid argtype = exprType((Node *) aggref->target); + Oid argtypes[FUNC_MAX_ARGS]; + int nargs; + ListCell *l; + + nargs = 0; + foreach(l, aggref->args) + { + if (nargs >= FUNC_MAX_ARGS) + ereport(ERROR, + (errcode(ERRCODE_TOO_MANY_ARGUMENTS), + errmsg("too many arguments"))); + argtypes[nargs] = exprType((Node *) lfirst(l)); + nargs++; + } appendStringInfo(buf, "%s(%s", - generate_function_name(aggref->aggfnoid, 1, &argtype), + generate_function_name(aggref->aggfnoid, nargs, argtypes), aggref->aggdistinct ? "DISTINCT " : ""); + /* aggstar can be set only in zero-argument aggregates */ if (aggref->aggstar) - appendStringInfo(buf, "*"); + appendStringInfoChar(buf, '*'); else - get_rule_expr((Node *) aggref->target, context, true); + get_rule_expr((Node *) aggref->args, context, true); appendStringInfoChar(buf, ')'); } diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 19f2983424c..ea697739191 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -12,7 +12,7 @@ * by PostgreSQL * * IDENTIFICATION - * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.441 2006/07/14 14:52:26 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.442 2006/07/27 19:52:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2325,7 +2325,8 @@ getAggregates(int *numAggs) int i_oid; int i_aggname; int i_aggnamespace; - int i_aggbasetype; + int i_pronargs; + int i_proargtypes; int i_rolname; int i_aggacl; @@ -2334,11 +2335,25 @@ getAggregates(int *numAggs) /* find all user-defined aggregates */ - if (g_fout->remoteVersion >= 70300) + if (g_fout->remoteVersion >= 80200) { appendPQExpBuffer(query, "SELECT tableoid, oid, proname as aggname, " "pronamespace as aggnamespace, " - "proargtypes[0] as aggbasetype, " + "pronargs, proargtypes, " + "(%s proowner) as rolname, " + "proacl as aggacl " + "FROM pg_proc " + "WHERE proisagg " + "AND pronamespace != " + "(select oid from pg_namespace where nspname = 'pg_catalog')", + username_subquery); + } + else if (g_fout->remoteVersion >= 70300) + { + appendPQExpBuffer(query, "SELECT tableoid, oid, proname as aggname, " + "pronamespace as aggnamespace, " + "CASE WHEN proargtypes[0] = 'pg_catalog.\"any\"'::pg_catalog.regtype THEN 0 ELSE 1 END as pronargs, " + "proargtypes, " "(%s proowner) as rolname, " "proacl as aggacl " "FROM pg_proc " @@ -2351,7 +2366,8 @@ getAggregates(int *numAggs) { appendPQExpBuffer(query, "SELECT tableoid, oid, aggname, " "0::oid as aggnamespace, " - "aggbasetype, " + "CASE WHEN aggbasetype = 0 THEN 0 ELSE 1 END as pronargs, " + "aggbasetype as proargtypes, " "(%s aggowner) as rolname, " "'{=X}' as aggacl " "FROM pg_aggregate " @@ -2365,7 +2381,8 @@ getAggregates(int *numAggs) "(SELECT oid FROM pg_class WHERE relname = 'pg_aggregate') AS tableoid, " "oid, aggname, " "0::oid as aggnamespace, " - "aggbasetype, " + "CASE WHEN aggbasetype = 0 THEN 0 ELSE 1 END as pronargs, " + "aggbasetype as proargtypes, " "(%s aggowner) as rolname, " "'{=X}' as aggacl " "FROM pg_aggregate " @@ -2386,7 +2403,8 @@ getAggregates(int *numAggs) i_oid = PQfnumber(res, "oid"); i_aggname = PQfnumber(res, "aggname"); i_aggnamespace = PQfnumber(res, "aggnamespace"); - i_aggbasetype = PQfnumber(res, "aggbasetype"); + i_pronargs = PQfnumber(res, "pronargs"); + i_proargtypes = PQfnumber(res, "proargtypes"); i_rolname = PQfnumber(res, "rolname"); i_aggacl = PQfnumber(res, "aggacl"); @@ -2404,13 +2422,21 @@ getAggregates(int *numAggs) write_msg(NULL, "WARNING: owner of aggregate function \"%s\" appears to be invalid\n", agginfo[i].aggfn.dobj.name); agginfo[i].aggfn.lang = InvalidOid; /* not currently interesting */ - agginfo[i].aggfn.nargs = 1; - agginfo[i].aggfn.argtypes = (Oid *) malloc(sizeof(Oid)); - agginfo[i].aggfn.argtypes[0] = atooid(PQgetvalue(res, i, i_aggbasetype)); agginfo[i].aggfn.prorettype = InvalidOid; /* not saved */ agginfo[i].aggfn.proacl = strdup(PQgetvalue(res, i, i_aggacl)); - agginfo[i].anybasetype = false; /* computed when it's dumped */ - agginfo[i].fmtbasetype = NULL; /* computed when it's dumped */ + agginfo[i].aggfn.nargs = atoi(PQgetvalue(res, i, i_pronargs)); + if (agginfo[i].aggfn.nargs == 0) + agginfo[i].aggfn.argtypes = NULL; + else + { + agginfo[i].aggfn.argtypes = (Oid *) malloc(agginfo[i].aggfn.nargs * sizeof(Oid)); + if (g_fout->remoteVersion >= 70300) + parseOidArray(PQgetvalue(res, i, i_proargtypes), + agginfo[i].aggfn.argtypes, + agginfo[i].aggfn.nargs); + else /* it's just aggbasetype */ + agginfo[i].aggfn.argtypes[0] = atooid(PQgetvalue(res, i, i_proargtypes)); + } /* Decide whether we want to dump it */ selectDumpableObject(&(agginfo[i].aggfn.dobj)); @@ -6759,6 +6785,7 @@ static char * format_aggregate_signature(AggInfo *agginfo, Archive *fout, bool honor_quotes) { PQExpBufferData buf; + int j; initPQExpBuffer(&buf); if (honor_quotes) @@ -6767,23 +6794,24 @@ format_aggregate_signature(AggInfo *agginfo, Archive *fout, bool honor_quotes) else appendPQExpBuffer(&buf, "%s", agginfo->aggfn.dobj.name); - /* If using regtype or format_type, fmtbasetype is already quoted */ - if (fout->remoteVersion >= 70100) - { - if (agginfo->anybasetype) - appendPQExpBuffer(&buf, "(*)"); - else - appendPQExpBuffer(&buf, "(%s)", agginfo->fmtbasetype); - } + if (agginfo->aggfn.nargs == 0) + appendPQExpBuffer(&buf, "(*)"); else { - if (agginfo->anybasetype) - appendPQExpBuffer(&buf, "(*)"); - else - appendPQExpBuffer(&buf, "(%s)", - fmtId(agginfo->fmtbasetype)); - } + appendPQExpBuffer(&buf, "("); + for (j = 0; j < agginfo->aggfn.nargs; j++) + { + char *typname; + + typname = getFormattedTypeName(agginfo->aggfn.argtypes[j], zeroAsOpaque); + appendPQExpBuffer(&buf, "%s%s", + (j > 0) ? ", " : "", + typname); + free(typname); + } + appendPQExpBuffer(&buf, ")"); + } return buf.data; } @@ -6807,8 +6835,6 @@ dumpAgg(Archive *fout, AggInfo *agginfo) int i_aggsortop; int i_aggtranstype; int i_agginitval; - int i_anybasetype; - int i_fmtbasetype; int i_convertok; const char *aggtransfn; const char *aggfinalfn; @@ -6836,8 +6862,6 @@ dumpAgg(Archive *fout, AggInfo *agginfo) "aggfinalfn, aggtranstype::pg_catalog.regtype, " "aggsortop::pg_catalog.regoperator, " "agginitval, " - "proargtypes[0] = 'pg_catalog.\"any\"'::pg_catalog.regtype as anybasetype, " - "proargtypes[0]::pg_catalog.regtype as fmtbasetype, " "'t'::boolean as convertok " "from pg_catalog.pg_aggregate a, pg_catalog.pg_proc p " "where a.aggfnoid = p.oid " @@ -6850,8 +6874,6 @@ dumpAgg(Archive *fout, AggInfo *agginfo) "aggfinalfn, aggtranstype::pg_catalog.regtype, " "0 as aggsortop, " "agginitval, " - "proargtypes[0] = 'pg_catalog.\"any\"'::pg_catalog.regtype as anybasetype, " - "proargtypes[0]::pg_catalog.regtype as fmtbasetype, " "'t'::boolean as convertok " "from pg_catalog.pg_aggregate a, pg_catalog.pg_proc p " "where a.aggfnoid = p.oid " @@ -6864,9 +6886,6 @@ dumpAgg(Archive *fout, AggInfo *agginfo) "format_type(aggtranstype, NULL) as aggtranstype, " "0 as aggsortop, " "agginitval, " - "aggbasetype = 0 as anybasetype, " - "CASE WHEN aggbasetype = 0 THEN '-' " - "ELSE format_type(aggbasetype, NULL) END as fmtbasetype, " "'t'::boolean as convertok " "from pg_aggregate " "where oid = '%u'::oid", @@ -6879,8 +6898,6 @@ dumpAgg(Archive *fout, AggInfo *agginfo) "(select typname from pg_type where oid = aggtranstype1) as aggtranstype, " "0 as aggsortop, " "agginitval1 as agginitval, " - "aggbasetype = 0 as anybasetype, " - "(select typname from pg_type where oid = aggbasetype) as fmtbasetype, " "(aggtransfn2 = 0 and aggtranstype2 = 0 and agginitval2 is null) as convertok " "from pg_aggregate " "where oid = '%u'::oid", @@ -6904,8 +6921,6 @@ dumpAgg(Archive *fout, AggInfo *agginfo) i_aggsortop = PQfnumber(res, "aggsortop"); i_aggtranstype = PQfnumber(res, "aggtranstype"); i_agginitval = PQfnumber(res, "agginitval"); - i_anybasetype = PQfnumber(res, "anybasetype"); - i_fmtbasetype = PQfnumber(res, "fmtbasetype"); i_convertok = PQfnumber(res, "convertok"); aggtransfn = PQgetvalue(res, 0, i_aggtransfn); @@ -6913,10 +6928,6 @@ dumpAgg(Archive *fout, AggInfo *agginfo) aggsortop = PQgetvalue(res, 0, i_aggsortop); aggtranstype = PQgetvalue(res, 0, i_aggtranstype); agginitval = PQgetvalue(res, 0, i_agginitval); - /* we save anybasetype for format_aggregate_signature */ - agginfo->anybasetype = (PQgetvalue(res, 0, i_anybasetype)[0] == 't'); - /* we save fmtbasetype for format_aggregate_signature */ - agginfo->fmtbasetype = strdup(PQgetvalue(res, 0, i_fmtbasetype)); convertok = (PQgetvalue(res, 0, i_convertok)[0] == 't'); aggsig = format_aggregate_signature(agginfo, fout, true); @@ -6932,27 +6943,20 @@ dumpAgg(Archive *fout, AggInfo *agginfo) if (g_fout->remoteVersion >= 70300) { /* If using 7.3's regproc or regtype, data is already quoted */ - appendPQExpBuffer(details, " BASETYPE = %s,\n SFUNC = %s,\n STYPE = %s", - agginfo->anybasetype ? "'any'" : - agginfo->fmtbasetype, + appendPQExpBuffer(details, " SFUNC = %s,\n STYPE = %s", aggtransfn, aggtranstype); } else if (g_fout->remoteVersion >= 70100) { /* format_type quotes, regproc does not */ - appendPQExpBuffer(details, " BASETYPE = %s,\n SFUNC = %s,\n STYPE = %s", - agginfo->anybasetype ? "'any'" : - agginfo->fmtbasetype, + appendPQExpBuffer(details, " SFUNC = %s,\n STYPE = %s", fmtId(aggtransfn), aggtranstype); } else { /* need quotes all around */ - appendPQExpBuffer(details, " BASETYPE = %s,\n", - agginfo->anybasetype ? "'any'" : - fmtId(agginfo->fmtbasetype)); appendPQExpBuffer(details, " SFUNC = %s,\n", fmtId(aggtransfn)); appendPQExpBuffer(details, " STYPE = %s", @@ -6986,8 +6990,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo) aggsig); appendPQExpBuffer(q, "CREATE AGGREGATE %s (\n%s\n);\n", - fmtId(agginfo->aggfn.dobj.name), - details->data); + aggsig, details->data); ArchiveEntry(fout, agginfo->aggfn.dobj.catId, agginfo->aggfn.dobj.dumpId, aggsig_tag, @@ -7008,7 +7011,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 aggregates on ANY. + * syntax for zero-argument aggregates. */ free(aggsig); free(aggsig_tag); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 33b0fbf0589..738eff36ac8 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.126 2006/07/02 02:23:21 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.127 2006/07/27 19:52:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -147,8 +147,7 @@ typedef struct _funcInfo typedef struct _aggInfo { FuncInfo aggfn; - bool anybasetype; /* is the basetype "any"? */ - char *fmtbasetype; /* formatted type name */ + /* we don't require any other fields at the moment */ } AggInfo; typedef struct _oprInfo diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index ca7d01a6f88..4e4f2dd4b3e 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -3,7 +3,7 @@ * * Copyright (c) 2000-2006, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.141 2006/07/17 00:21:23 neilc Exp $ + * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.142 2006/07/27 19:52:06 tgl Exp $ */ #include "postgres_fe.h" #include "describe.h" @@ -67,17 +67,22 @@ describeAggregates(const char *pattern, bool verbose) printfPQExpBuffer(&buf, "SELECT n.nspname as \"%s\",\n" " p.proname AS \"%s\",\n" - " CASE p.proargtypes[0]\n" - " WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype\n" - " THEN CAST('%s' AS pg_catalog.text)\n" - " ELSE pg_catalog.format_type(p.proargtypes[0], NULL)\n" + " CASE WHEN p.pronargs = 0\n" + " THEN CAST('*' AS pg_catalog.text)\n" + " ELSE\n" + " pg_catalog.array_to_string(ARRAY(\n" + " SELECT\n" + " pg_catalog.format_type(p.proargtypes[s.i], NULL)\n" + " FROM\n" + " pg_catalog.generate_series(0, pg_catalog.array_upper(p.proargtypes, 1)) AS s(i)\n" + " ), ', ')\n" " END AS \"%s\",\n" " pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"\n" "FROM pg_catalog.pg_proc p\n" " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n" "WHERE p.proisagg\n", - _("Schema"), _("Name"), _("(all types)"), - _("Data type"), _("Description")); + _("Schema"), _("Name"), + _("Argument data types"), _("Description")); processNamePattern(&buf, pattern, true, false, "n.nspname", "p.proname", NULL, diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index bcb2232e0b1..00896bce2e2 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.341 2006/07/26 19:31:51 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.342 2006/07/27 19:52:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200607261 +#define CATALOG_VERSION_NO 200607271 #endif diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h index e9d5c5151ab..f77328b9e91 100644 --- a/src/include/catalog/pg_aggregate.h +++ b/src/include/catalog/pg_aggregate.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_aggregate.h,v 1.55 2006/07/21 20:51:33 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_aggregate.h,v 1.56 2006/07/27 19:52:06 tgl Exp $ * * NOTES * the genbki.sh script reads this file and generates .bki @@ -140,11 +140,9 @@ DATA(insert ( 2051 array_smaller - 1072 2277 _null_ )); DATA(insert ( 2245 bpchar_smaller - 1058 1042 _null_ )); DATA(insert ( 2798 tidsmaller - 2799 27 _null_ )); -/* - * Using int8inc for count() is cheating a little, since it really only - * takes 1 parameter not 2, but nodeAgg.c won't complain ... - */ -DATA(insert ( 2147 int8inc - 0 20 0 )); +/* count */ +DATA(insert ( 2147 int8inc_any - 0 20 "0" )); +DATA(insert ( 2803 int8inc - 0 20 "0" )); /* var_pop */ DATA(insert ( 2718 int8_accum numeric_var_pop 0 1231 "{0,0,0}" )); @@ -214,7 +212,8 @@ DATA(insert ( 2243 bitor - 0 1560 _null_ )); */ extern void AggregateCreate(const char *aggName, Oid aggNamespace, - Oid aggBaseType, + Oid *aggArgTypes, + int numArgs, List *aggtransfnName, List *aggfinalfnName, List *aggsortopName, diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 7f68894d8d3..729f92d1bf4 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.417 2006/07/25 03:51:21 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.418 2006/07/27 19:52:06 tgl Exp $ * * NOTES * The script catalog/genbki.sh reads this file and generates .bki @@ -1534,6 +1534,8 @@ DESCR("truncate interval to specified units"); DATA(insert OID = 1219 ( int8inc PGNSP PGUID 12 f f t f i 1 20 "20" _null_ _null_ _null_ int8inc - _null_ )); DESCR("increment"); +DATA(insert OID = 2804 ( int8inc_any PGNSP PGUID 12 f f t f i 2 20 "20 2276" _null_ _null_ _null_ int8inc - _null_ )); +DESCR("increment, ignores second argument"); DATA(insert OID = 1230 ( int8abs PGNSP PGUID 12 f f t f i 1 20 "20" _null_ _null_ _null_ int8abs - _null_ )); DESCR("absolute value"); @@ -3148,7 +3150,9 @@ DATA(insert OID = 2051 ( min PGNSP PGUID 12 t f f f i 1 2277 "2277" _null_ _ DATA(insert OID = 2245 ( min PGNSP PGUID 12 t f f f i 1 1042 "1042" _null_ _null_ _null_ aggregate_dummy - _null_ )); DATA(insert OID = 2798 ( min PGNSP PGUID 12 t f f f i 1 27 "27" _null_ _null_ _null_ aggregate_dummy - _null_ )); +/* count has two forms: count(any) and count(*) */ DATA(insert OID = 2147 ( count PGNSP PGUID 12 t f f f i 1 20 "2276" _null_ _null_ _null_ aggregate_dummy - _null_ )); +DATA(insert OID = 2803 ( count PGNSP PGUID 12 t f f f i 0 20 "" _null_ _null_ _null_ aggregate_dummy - _null_ )); DATA(insert OID = 2718 ( var_pop PGNSP PGUID 12 t f f f i 1 1700 "20" _null_ _null_ _null_ aggregate_dummy - _null_ )); DATA(insert OID = 2719 ( var_pop PGNSP PGUID 12 t f f f i 1 1700 "23" _null_ _null_ _null_ aggregate_dummy - _null_ )); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index e03daed8e7f..8dec4130e28 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.154 2006/07/26 00:34:48 momjian Exp $ + * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.155 2006/07/27 19:52:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -449,7 +449,7 @@ typedef struct GenericExprState typedef struct AggrefExprState { ExprState xprstate; - ExprState *target; /* state of my child node */ + List *args; /* states of argument expressions */ int aggno; /* ID number for agg within its plan node */ } AggrefExprState; diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index e289789de86..17c3a894b0b 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -10,7 +10,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.114 2006/07/13 16:49:19 momjian Exp $ + * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.115 2006/07/27 19:52:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -184,9 +184,9 @@ typedef struct Aggref Expr xpr; Oid aggfnoid; /* pg_proc Oid of the aggregate */ Oid aggtype; /* type Oid of result of the aggregate */ - Expr *target; /* expression we are aggregating on */ + List *args; /* arguments to the aggregate */ Index agglevelsup; /* > 0 if agg belongs to outer query */ - bool aggstar; /* TRUE if argument was really '*' */ + bool aggstar; /* TRUE if argument list was really '*' */ bool aggdistinct; /* TRUE if it's agg(DISTINCT ...) */ } Aggref; diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h index 68163f1abaf..1486a3a0b2c 100644 --- a/src/include/parser/parse_agg.h +++ b/src/include/parser/parse_agg.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/parser/parse_agg.h,v 1.33 2006/03/05 15:58:57 momjian Exp $ + * $PostgreSQL: pgsql/src/include/parser/parse_agg.h,v 1.34 2006/07/27 19:52:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -19,7 +19,8 @@ extern void transformAggregateCall(ParseState *pstate, Aggref *agg); extern void parseCheckAggregates(ParseState *pstate, Query *qry); -extern void build_aggregate_fnexprs(Oid agg_input_type, +extern void build_aggregate_fnexprs(Oid *agg_input_types, + int agg_num_inputs, Oid agg_state_type, Oid agg_result_type, Oid transfn_oid, diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out index 518315b3c1a..3b0c0f467a9 100644 --- a/src/test/regress/expected/aggregates.out +++ b/src/test/regress/expected/aggregates.out @@ -181,6 +181,7 @@ group by ten order by ten; 9 | 100 | 4 (10 rows) +-- user-defined aggregates SELECT newavg(four) AS avg_1 FROM onek; avg_1 -------------------- @@ -199,6 +200,24 @@ SELECT newcnt(four) AS cnt_1000 FROM onek; 1000 (1 row) +SELECT newcnt(*) AS cnt_1000 FROM onek; + cnt_1000 +---------- + 1000 +(1 row) + +SELECT oldcnt(*) AS cnt_1000 FROM onek; + cnt_1000 +---------- + 1000 +(1 row) + +SELECT sum2(q1,q2) FROM int8_tbl; + sum2 +------------------- + 18271560493827981 +(1 row) + -- test for outer-level aggregates -- this should work select ten, sum(distinct four) from onek a diff --git a/src/test/regress/expected/create_aggregate.out b/src/test/regress/expected/create_aggregate.out index b0fec460cbb..08daaa8ee3b 100644 --- a/src/test/regress/expected/create_aggregate.out +++ b/src/test/regress/expected/create_aggregate.out @@ -17,12 +17,29 @@ CREATE AGGREGATE newsum ( sfunc1 = int4pl, basetype = int4, stype1 = int4, initcond1 = '0' ); --- value-independent transition function -CREATE AGGREGATE newcnt ( - sfunc = int4inc, basetype = 'any', stype = int4, +-- zero-argument aggregate +CREATE AGGREGATE newcnt (*) ( + sfunc = int8inc, stype = int8, + initcond = '0' +); +-- old-style spelling of same +CREATE AGGREGATE oldcnt ( + sfunc = int8inc, basetype = 'ANY', stype = int8, + initcond = '0' +); +-- aggregate that only cares about null/nonnull input +CREATE AGGREGATE newcnt ("any") ( + sfunc = int8inc_any, stype = int8, + initcond = '0' +); +-- multi-argument aggregate +create function sum3(int8,int8,int8) returns int8 as +'select $1 + $2 + $3' language sql strict immutable; +create aggregate sum2(int8,int8) ( + sfunc = sum3, stype = int8, initcond = '0' ); COMMENT ON AGGREGATE nosuchagg (*) IS 'should fail'; ERROR: aggregate nosuchagg(*) does not exist -COMMENT ON AGGREGATE newcnt (*) IS 'an any agg comment'; -COMMENT ON AGGREGATE newcnt (*) IS NULL; +COMMENT ON AGGREGATE newcnt (*) IS 'an agg(*) comment'; +COMMENT ON AGGREGATE newcnt ("any") IS 'an agg(any) comment'; diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 1161d0474c4..5c905f55a54 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -51,7 +51,7 @@ WHERE p1.prolang = 0 OR p1.prorettype = 0 OR -- Look for conflicting proc definitions (same names and input datatypes). -- (This test should be dead code now that we have the unique index --- pg_proc_proname_narg_type_index, but I'll leave it in anyway.) +-- pg_proc_proname_args_nsp_index, but I'll leave it in anyway.) SELECT p1.oid, p1.proname, p2.oid, p2.proname FROM pg_proc AS p1, pg_proc AS p2 WHERE p1.oid != p2.oid AND @@ -67,11 +67,14 @@ WHERE p1.oid != p2.oid AND -- have several entries with different pronames for the same internal function, -- but conflicts in the number of arguments and other critical items should -- be complained of. +-- Ignore aggregates, since they all use "aggregate_dummy". +-- As of 8.2, this finds int8inc and int8inc_any, which are OK. SELECT p1.oid, p1.proname, p2.oid, p2.proname FROM pg_proc AS p1, pg_proc AS p2 -WHERE p1.oid != p2.oid AND +WHERE p1.oid < p2.oid AND p1.prosrc = p2.prosrc AND p1.prolang = 12 AND p2.prolang = 12 AND + p1.proisagg = false AND p2.proisagg = false AND (p1.prolang != p2.prolang OR p1.proisagg != p2.proisagg OR p1.prosecdef != p2.prosecdef OR @@ -79,9 +82,10 @@ WHERE p1.oid != p2.oid AND p1.proretset != p2.proretset OR p1.provolatile != p2.provolatile OR p1.pronargs != p2.pronargs); - oid | proname | oid | proname ------+---------+-----+--------- -(0 rows) + oid | proname | oid | proname +------+---------+------+------------- + 1219 | int8inc | 2804 | int8inc_any +(1 row) -- Look for uses of different type OIDs in the argument/result type fields -- for different aliases of the same built-in function. @@ -617,7 +621,7 @@ WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0; SELECT a.aggfnoid::oid, p.proname FROM pg_aggregate as a, pg_proc as p WHERE a.aggfnoid = p.oid AND - (NOT p.proisagg OR p.pronargs != 1 OR p.proretset); + (NOT p.proisagg OR p.proretset); aggfnoid | proname ----------+--------- (0 rows) @@ -648,13 +652,17 @@ FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS ptr WHERE a.aggfnoid = p.oid AND a.aggtransfn = ptr.oid AND (ptr.proretset + OR NOT (ptr.pronargs = p.pronargs + 1) OR NOT physically_coercible(ptr.prorettype, a.aggtranstype) OR NOT physically_coercible(a.aggtranstype, ptr.proargtypes[0]) - OR NOT ((ptr.pronargs = 2 AND - physically_coercible(p.proargtypes[0], ptr.proargtypes[1])) - OR - (ptr.pronargs = 1 AND - p.proargtypes[0] = '"any"'::regtype))); + OR (p.pronargs > 0 AND + NOT physically_coercible(p.proargtypes[0], ptr.proargtypes[1])) + OR (p.pronargs > 1 AND + NOT physically_coercible(p.proargtypes[1], ptr.proargtypes[2])) + OR (p.pronargs > 2 AND + NOT physically_coercible(p.proargtypes[2], ptr.proargtypes[3])) + -- we could carry the check further, but that's enough for now + ); aggfnoid | proname | oid | proname ----------+---------+-----+--------- (0 rows) diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out index 57a12583319..841d77c78e5 100644 --- a/src/test/regress/expected/polymorphism.out +++ b/src/test/regress/expected/polymorphism.out @@ -50,6 +50,9 @@ CREATE FUNCTION tf1p(anyarray,int) RETURNS anyarray AS -- arg2 only polymorphic transfn CREATE FUNCTION tf2p(int[],anyelement) RETURNS int[] AS 'select $1' LANGUAGE SQL; +-- multi-arg polymorphic +CREATE FUNCTION sum3(anyelement,anyelement,anyelement) returns anyelement AS +'select $1+$2+$3' language sql strict; -- finalfn polymorphic CREATE FUNCTION ffp(anyarray) RETURNS anyarray AS 'select $1' LANGUAGE SQL; @@ -70,30 +73,30 @@ CREATE FUNCTION ffnp(int[]) returns int[] as -- ------- -- N N -- should CREATE -CREATE AGGREGATE myaggp01a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = int4[], +CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[], FINALFUNC = ffp, INITCOND = '{}'); -- P N -- should ERROR: stfnp(anyarray) not matched by stfnp(int[]) -CREATE AGGREGATE myaggp02a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = anyarray, +CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type. +DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. -- N P -- should CREATE -CREATE AGGREGATE myaggp03a(BASETYPE = "ANY", SFUNC = stfp, STYPE = int4[], +CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[], FINALFUNC = ffp, INITCOND = '{}'); -CREATE AGGREGATE myaggp03b(BASETYPE = "ANY", SFUNC = stfp, STYPE = int4[], +CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[], INITCOND = '{}'); -- P P -- should ERROR: we have no way to resolve S -CREATE AGGREGATE myaggp04a(BASETYPE = "ANY", SFUNC = stfp, STYPE = anyarray, +CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type. -CREATE AGGREGATE myaggp04b(BASETYPE = "ANY", SFUNC = stfp, STYPE = anyarray, +DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type. +DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. -- Case2 (R = P) && ((B = P) || (B = N)) -- ------------------------------------- -- S tf1 B tf2 @@ -148,13 +151,13 @@ ERROR: function tfp(integer[], anyelement) does not exist CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type. +DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. -- P N N P -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement) CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type. +DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. -- P N P N -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int) CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp, @@ -170,21 +173,21 @@ ERROR: function tf2p(anyarray, anyelement) does not exist CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type. +DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type. +DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. -- P P N P -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement) CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type. +DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type. +DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. -- P P P N -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int) CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p, @@ -205,30 +208,30 @@ CREATE AGGREGATE myaggp20b(BASETYPE = anyelement, SFUNC = tfp, -- ------- -- N N -- should CREATE -CREATE AGGREGATE myaggn01a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = int4[], +CREATE AGGREGATE myaggn01a(*) (SFUNC = stfnp, STYPE = int4[], FINALFUNC = ffnp, INITCOND = '{}'); -CREATE AGGREGATE myaggn01b(BASETYPE = "ANY", SFUNC = stfnp, STYPE = int4[], +CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[], INITCOND = '{}'); -- P N -- should ERROR: stfnp(anyarray) not matched by stfnp(int[]) -CREATE AGGREGATE myaggn02a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = anyarray, +CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type. -CREATE AGGREGATE myaggn02b(BASETYPE = "ANY", SFUNC = stfnp, STYPE = anyarray, +DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type. +DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. -- N P -- should CREATE -CREATE AGGREGATE myaggn03a(BASETYPE = "ANY", SFUNC = stfp, STYPE = int4[], +CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[], FINALFUNC = ffnp, INITCOND = '{}'); -- P P -- should ERROR: ffnp(anyarray) not matched by ffnp(int[]) -CREATE AGGREGATE myaggn04a(BASETYPE = "ANY", SFUNC = stfp, STYPE = anyarray, +CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type. +DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. -- Case4 (R = N) && ((B = P) || (B = N)) -- ------------------------------------- -- S tf1 B tf2 @@ -282,21 +285,21 @@ ERROR: function tfp(integer[], anyelement) does not exist CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type. +DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type. +DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. -- P N N P -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement) CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type. +DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type. +DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. -- P N P N -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int) CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp, @@ -318,13 +321,13 @@ ERROR: function tf2p(anyarray, anyelement) does not exist CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type. +DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. -- P P N P -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement) CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type. +DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. -- P P P N -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int) CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p, @@ -335,6 +338,9 @@ ERROR: function tf1p(anyarray, anyelement) does not exist CREATE AGGREGATE myaggn20a(BASETYPE = anyelement, SFUNC = tfp, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: function ffnp(anyarray) does not exist +-- multi-arg polymorphic +CREATE AGGREGATE mysum2(anyelement,anyelement) (SFUNC = sum3, + STYPE = anyelement, INITCOND = '0'); -- create test data for polymorphic aggregates create temp table t(f1 int, f2 int[], f3 text); insert into t values(1,array[1],'a'); @@ -530,3 +536,9 @@ select f3, myaggn10a(f1) from t group by f3; a | {1,2,3} (3 rows) +select mysum2(f1, f1 + 1) from t; + mysum2 +-------- + 38 +(1 row) + diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql index a9429525cab..1c2a6044500 100644 --- a/src/test/regress/sql/aggregates.sql +++ b/src/test/regress/sql/aggregates.sql @@ -48,11 +48,13 @@ group by ten order by ten; select ten, count(four), sum(DISTINCT four) from onek group by ten order by ten; - +-- user-defined aggregates SELECT newavg(four) AS avg_1 FROM onek; SELECT newsum(four) AS sum_1500 FROM onek; SELECT newcnt(four) AS cnt_1000 FROM onek; - +SELECT newcnt(*) AS cnt_1000 FROM onek; +SELECT oldcnt(*) AS cnt_1000 FROM onek; +SELECT sum2(q1,q2) FROM int8_tbl; -- test for outer-level aggregates diff --git a/src/test/regress/sql/create_aggregate.sql b/src/test/regress/sql/create_aggregate.sql index 4188760c87c..891b0e0892b 100644 --- a/src/test/regress/sql/create_aggregate.sql +++ b/src/test/regress/sql/create_aggregate.sql @@ -20,12 +20,33 @@ CREATE AGGREGATE newsum ( initcond1 = '0' ); --- value-independent transition function -CREATE AGGREGATE newcnt ( - sfunc = int4inc, basetype = 'any', stype = int4, +-- zero-argument aggregate +CREATE AGGREGATE newcnt (*) ( + sfunc = int8inc, stype = int8, + initcond = '0' +); + +-- old-style spelling of same +CREATE AGGREGATE oldcnt ( + sfunc = int8inc, basetype = 'ANY', stype = int8, + initcond = '0' +); + +-- aggregate that only cares about null/nonnull input +CREATE AGGREGATE newcnt ("any") ( + sfunc = int8inc_any, stype = int8, + initcond = '0' +); + +-- multi-argument aggregate +create function sum3(int8,int8,int8) returns int8 as +'select $1 + $2 + $3' language sql strict immutable; + +create aggregate sum2(int8,int8) ( + sfunc = sum3, stype = int8, initcond = '0' ); COMMENT ON AGGREGATE nosuchagg (*) IS 'should fail'; -COMMENT ON AGGREGATE newcnt (*) IS 'an any agg comment'; -COMMENT ON AGGREGATE newcnt (*) IS NULL; +COMMENT ON AGGREGATE newcnt (*) IS 'an agg(*) comment'; +COMMENT ON AGGREGATE newcnt ("any") IS 'an agg(any) comment'; diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql index 7b1d2b54cba..84d0ce93b5c 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -55,7 +55,7 @@ WHERE p1.prolang = 0 OR p1.prorettype = 0 OR -- Look for conflicting proc definitions (same names and input datatypes). -- (This test should be dead code now that we have the unique index --- pg_proc_proname_narg_type_index, but I'll leave it in anyway.) +-- pg_proc_proname_args_nsp_index, but I'll leave it in anyway.) SELECT p1.oid, p1.proname, p2.oid, p2.proname FROM pg_proc AS p1, pg_proc AS p2 @@ -69,12 +69,16 @@ WHERE p1.oid != p2.oid AND -- have several entries with different pronames for the same internal function, -- but conflicts in the number of arguments and other critical items should -- be complained of. +-- Ignore aggregates, since they all use "aggregate_dummy". + +-- As of 8.2, this finds int8inc and int8inc_any, which are OK. SELECT p1.oid, p1.proname, p2.oid, p2.proname FROM pg_proc AS p1, pg_proc AS p2 -WHERE p1.oid != p2.oid AND +WHERE p1.oid < p2.oid AND p1.prosrc = p2.prosrc AND p1.prolang = 12 AND p2.prolang = 12 AND + p1.proisagg = false AND p2.proisagg = false AND (p1.prolang != p2.prolang OR p1.proisagg != p2.proisagg OR p1.prosecdef != p2.prosecdef OR @@ -515,7 +519,7 @@ WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0; SELECT a.aggfnoid::oid, p.proname FROM pg_aggregate as a, pg_proc as p WHERE a.aggfnoid = p.oid AND - (NOT p.proisagg OR p.pronargs != 1 OR p.proretset); + (NOT p.proisagg OR p.proretset); -- Make sure there are no proisagg pg_proc entries without matches. @@ -539,13 +543,17 @@ FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS ptr WHERE a.aggfnoid = p.oid AND a.aggtransfn = ptr.oid AND (ptr.proretset + OR NOT (ptr.pronargs = p.pronargs + 1) OR NOT physically_coercible(ptr.prorettype, a.aggtranstype) OR NOT physically_coercible(a.aggtranstype, ptr.proargtypes[0]) - OR NOT ((ptr.pronargs = 2 AND - physically_coercible(p.proargtypes[0], ptr.proargtypes[1])) - OR - (ptr.pronargs = 1 AND - p.proargtypes[0] = '"any"'::regtype))); + OR (p.pronargs > 0 AND + NOT physically_coercible(p.proargtypes[0], ptr.proargtypes[1])) + OR (p.pronargs > 1 AND + NOT physically_coercible(p.proargtypes[1], ptr.proargtypes[2])) + OR (p.pronargs > 2 AND + NOT physically_coercible(p.proargtypes[2], ptr.proargtypes[3])) + -- we could carry the check further, but that's enough for now + ); -- Cross-check finalfn (if present) against its entry in pg_proc. diff --git a/src/test/regress/sql/polymorphism.sql b/src/test/regress/sql/polymorphism.sql index 13d691a9e80..218a0a72b38 100644 --- a/src/test/regress/sql/polymorphism.sql +++ b/src/test/regress/sql/polymorphism.sql @@ -57,6 +57,10 @@ CREATE FUNCTION tf1p(anyarray,int) RETURNS anyarray AS CREATE FUNCTION tf2p(int[],anyelement) RETURNS int[] AS 'select $1' LANGUAGE SQL; +-- multi-arg polymorphic +CREATE FUNCTION sum3(anyelement,anyelement,anyelement) returns anyelement AS +'select $1+$2+$3' language sql strict; + -- finalfn polymorphic CREATE FUNCTION ffp(anyarray) RETURNS anyarray AS 'select $1' LANGUAGE SQL; @@ -78,26 +82,26 @@ CREATE FUNCTION ffnp(int[]) returns int[] as -- ------- -- N N -- should CREATE -CREATE AGGREGATE myaggp01a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = int4[], +CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[], FINALFUNC = ffp, INITCOND = '{}'); -- P N -- should ERROR: stfnp(anyarray) not matched by stfnp(int[]) -CREATE AGGREGATE myaggp02a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = anyarray, +CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); -- N P -- should CREATE -CREATE AGGREGATE myaggp03a(BASETYPE = "ANY", SFUNC = stfp, STYPE = int4[], +CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[], FINALFUNC = ffp, INITCOND = '{}'); -CREATE AGGREGATE myaggp03b(BASETYPE = "ANY", SFUNC = stfp, STYPE = int4[], +CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[], INITCOND = '{}'); -- P P -- should ERROR: we have no way to resolve S -CREATE AGGREGATE myaggp04a(BASETYPE = "ANY", SFUNC = stfp, STYPE = anyarray, +CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); -CREATE AGGREGATE myaggp04b(BASETYPE = "ANY", SFUNC = stfp, STYPE = anyarray, +CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray, INITCOND = '{}'); @@ -207,26 +211,26 @@ CREATE AGGREGATE myaggp20b(BASETYPE = anyelement, SFUNC = tfp, -- ------- -- N N -- should CREATE -CREATE AGGREGATE myaggn01a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = int4[], +CREATE AGGREGATE myaggn01a(*) (SFUNC = stfnp, STYPE = int4[], FINALFUNC = ffnp, INITCOND = '{}'); -CREATE AGGREGATE myaggn01b(BASETYPE = "ANY", SFUNC = stfnp, STYPE = int4[], +CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[], INITCOND = '{}'); -- P N -- should ERROR: stfnp(anyarray) not matched by stfnp(int[]) -CREATE AGGREGATE myaggn02a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = anyarray, +CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); -CREATE AGGREGATE myaggn02b(BASETYPE = "ANY", SFUNC = stfnp, STYPE = anyarray, +CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray, INITCOND = '{}'); -- N P -- should CREATE -CREATE AGGREGATE myaggn03a(BASETYPE = "ANY", SFUNC = stfp, STYPE = int4[], +CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[], FINALFUNC = ffnp, INITCOND = '{}'); -- P P -- should ERROR: ffnp(anyarray) not matched by ffnp(int[]) -CREATE AGGREGATE myaggn04a(BASETYPE = "ANY", SFUNC = stfp, STYPE = anyarray, +CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); @@ -330,6 +334,10 @@ CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p, CREATE AGGREGATE myaggn20a(BASETYPE = anyelement, SFUNC = tfp, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); +-- multi-arg polymorphic +CREATE AGGREGATE mysum2(anyelement,anyelement) (SFUNC = sum3, + STYPE = anyelement, INITCOND = '0'); + -- create test data for polymorphic aggregates create temp table t(f1 int, f2 int[], f3 text); insert into t values(1,array[1],'a'); @@ -365,3 +373,4 @@ select f3, myaggn08a(f1) from t group by f3; select f3, myaggn08b(f1) from t group by f3; select f3, myaggn09a(f1) from t group by f3; select f3, myaggn10a(f1) from t group by f3; +select mysum2(f1, f1 + 1) from t; -- GitLab