]> granicus.if.org Git - postgresql/commitdiff
Aggregate functions now support multiple input arguments. I also took
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 27 Jul 2006 19:52:07 +0000 (19:52 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 27 Jul 2006 19:52:07 +0000 (19:52 +0000)
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.

39 files changed:
doc/src/sgml/ref/alter_aggregate.sgml
doc/src/sgml/ref/comment.sgml
doc/src/sgml/ref/create_aggregate.sgml
doc/src/sgml/ref/drop_aggregate.sgml
doc/src/sgml/ref/psql-ref.sgml
doc/src/sgml/sql.sgml
doc/src/sgml/syntax.sgml
doc/src/sgml/xaggr.sgml
src/backend/catalog/pg_aggregate.c
src/backend/commands/aggregatecmds.c
src/backend/executor/execQual.c
src/backend/executor/nodeAgg.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/plan/planagg.c
src/backend/optimizer/util/clauses.c
src/backend/parser/gram.y
src/backend/parser/parse_agg.c
src/backend/parser/parse_func.c
src/backend/utils/adt/ruleutils.c
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dump.h
src/bin/psql/describe.c
src/include/catalog/catversion.h
src/include/catalog/pg_aggregate.h
src/include/catalog/pg_proc.h
src/include/nodes/execnodes.h
src/include/nodes/primnodes.h
src/include/parser/parse_agg.h
src/test/regress/expected/aggregates.out
src/test/regress/expected/create_aggregate.out
src/test/regress/expected/opr_sanity.out
src/test/regress/expected/polymorphism.out
src/test/regress/sql/aggregates.sql
src/test/regress/sql/create_aggregate.sql
src/test/regress/sql/opr_sanity.sql
src/test/regress/sql/polymorphism.sql

index c3311b99ce77a785c8c1beb23165507f3c0b3f2d..a54969032c0015ad3cd4a6b48e7257c35f5b44da 100644 (file)
@@ -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>
index d8c0a3acab0ea310ff6b92d3e7c1ae7c114f0672..c340b142c5b4d81f9c052a9e310b1bf5b68dd1f9 100644 (file)
@@ -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>
index 5eb0741c9c216a9487834b62b191c8cb14ec936f..b43636c9bfaf9ce206b6e5d57eba43729533f5d7 100644 (file)
@@ -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>
index e6d03e0bdabffe51eb0031f20b8ee48ae513cdfa..749c06525ae73a235ba3a5ff0ecd26524a631c83 100644 (file)
@@ -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>
index 74a53368463f450701224faa2543db66c6508be2..97809bb7b19aeb62e9308fb5ea4fc78573d76945 100644 (file)
@@ -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=&gt;
         <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>
index b27400b4a456064e4cf879cd0dcea3d75c87105d..d8b2934063d2e1f5f62b5a9ca0b157a5d9e68e28 100644 (file)
@@ -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 &mdash;
-      i.e. the value of the aggregate operator is no longer calculated over
+      aggregate functions described above can be applied to the groups &mdash;
+      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>
 
index 74cc813a681f2cd3a98765ae2a2e4589517090f3..58c9ddad6d5a1c98f19ad366e62d6c7b9dafe7f3 100644 (file)
@@ -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 &mdash; 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">
index db6c077cbf61927aaa1da62f2c8373a95d402720..c02f21048a72c6b6c21e6f0b51aee99531455273 100644 (file)
@@ -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>
 
   <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:
 
index 47d21b35b01f5bef0e0c29c737bf5cf2b45991ab..747543e077d553ed496837d50c327b829d77aec8 100644 (file)
@@ -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);
index 40b4d59cb9cd8f2a2a43052cb1766a5c4b681c24..cb4dfee77e26b56eb8c5e7bfea4371efbe39b922 100644 (file)
@@ -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 */
index fc80472adee2883691571fac343b8344c7ae7ffd..7f341940d67a21c70c93be06eab23c792ce91dbd 100644 (file)
@@ -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.)
index 851a360d2f1446c9715db7f85a5657dda3b81aa1..19410997b2cee9b06212cf79848a6d3ba2f1bb6e 100644 (file)
@@ -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
  *       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;
                }
 
index a18f118bce4dce32b26c76f6df397860147a0b09..f2b2afd81af7572c8d949220161accb90bdfcbc9 100644 (file)
@@ -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);
index a1b758e7495bcd6b60df4105ff356da353790947..0122ebd629cce1db9bc166c21a6e2f318a26ccaf 100644 (file)
@@ -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);
index 1b85b9a8063d8543c5033e2d67f937c97f1b3d3a..61d49572ca8f7667bb9448c358cd73a249dfc44f 100644 (file)
@@ -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);
index 883155dcd0714e02e61f5826e12ccf2cdf2938ea..265a5b369ee9a1a15dbb99d785a440228b71d388 100644 (file)
@@ -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);
index 39e77897c0a6f2b44e626f5dfc3f2030c004bb7d..849b81a9a751c9b105d966458e29a014cba6d1b6 100644 (file)
@@ -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");
index 0196e4b3ff0e434392b0a84c13b1c123b8b3a6cd..dfc43149d3ad73a54c80c449b5c15402637f8011 100644 (file)
@@ -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;
index 754777c57bc4d822d56cb2e0f82bd45ed2b5c390..afd88e8125bdfbb38b32682ed5812fa6ffd62597 100644 (file)
@@ -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;
index 6d381cd2d9e79fa6a2ad088f575ed4e6327050fa..3bda907c994a592efe81a7b09089e3c582e1495e 100644 (file)
@@ -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,
index aa0632a389846d3a16cf288286dd8cd0514ac234..b1b53164f8091cedd298e9de6b0438ffe8d76a40 100644 (file)
@@ -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",
index 605efc60403129871c8567c1c809901b779cdeba..a2254b6e48169f1db6a92914eca7c41b433022bb 100644 (file)
@@ -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, ')');
 }
 
index 19f2983424ca4338521c55a77a98c78183469c37..ea697739191a70ff71b45896136794fdb8c2d2cd 100644 (file)
@@ -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);
index 33b0fbf05896cb0f69770a318f4271081cafc54a..738eff36ac8227f198ac00712c6030653d399d6e 100644 (file)
@@ -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
index ca7d01a6f8835eb71ecef5df0c489e896f5646ae..4e4f2dd4b3eed8c3660ce8c7ef8143577844d9a0 100644 (file)
@@ -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,
index bcb2232e0b151f49f8434b97067414014e52b483..00896bce2e2c299a48bf838eac25954c08706b05 100644 (file)
@@ -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
index e9d5c5151ab683421795e6fd26816a25e0f34d03..f77328b9e918c6299d1716c043c35a3d6f756ce7 100644 (file)
@@ -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,
index 7f68894d8d33b48a83f619f2b5e911cb400fad7b..729f92d1bf47a0be8431ae76ab4b904cb5fa17ba 100644 (file)
@@ -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_ ));
index e03daed8e7f272661c5c9ddcc0dbbf88724e0994..8dec4130e28bf0ab819c34692942e7c136f5afe9 100644 (file)
@@ -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;
 
index e289789de868b6008de594eacfc117917afe691c..17c3a894b0b4586486c3ad38f5c6d63ff819724b 100644 (file)
@@ -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;
 
index 68163f1abaf32c42b56d59aacfa9a1fee916bbb3..1486a3a0b2c94037f6a0d79c749e6d46399fcb08 100644 (file)
@@ -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,
index 518315b3c1a9aab25877076de06284a57e19e3d4..3b0c0f467a9b9f6537c7902621d8d0d7fcd69de5 100644 (file)
@@ -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
index b0fec460cbb39d556668a497d34670bf9390440a..08daaa8ee3bd002e404b02a8b395c94c10b14806 100644 (file)
@@ -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';
index 1161d0474c46d4372de53ff0bea4bf7b8fb5fb60..5c905f55a54b3be0bbcd27e878b43bdd9bf6cce0 100644 (file)
@@ -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)
index 57a125833192d54f123a2597fe47d76a6e47fe75..841d77c78e55033e359588e707bc376ea05d057b 100644 (file)
@@ -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)
+
index a9429525cab57a27af61eaf36fe56ca205057bfc..1c2a604450026a9e456f48a8b0c2db75d30d2576 100644 (file)
@@ -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
 
index 4188760c87c21ece71153a8115017ca3c7be32e4..891b0e0892b93b928f2fe36bbe1de45a2c074267 100644 (file)
@@ -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';
index 7b1d2b54cba630935e835f1feee4773d071f03c9..84d0ce93b5c2a54063786a8c0e747eda98841035 100644 (file)
@@ -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.
 
index 13d691a9e80285575056182a77821065357f84f4..218a0a72b3826857b859e93365df59bf5c48d025 100644 (file)
@@ -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;