<!--
-$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
-->
<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>
<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>
<!--
-$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
-->
{
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> |
<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>
<!--
-$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
-->
<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> ]
</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>,
<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>
<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.
<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.
<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>
<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>
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>
<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>
<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
<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>
<!--
-$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
-->
<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>
<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>
<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>
<!--
-$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
-->
<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>
-<!-- $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>
</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
<para>
<acronym>SQL</acronym> allows one to partition the tuples of a table
into groups. Then the
- aggregate operators described above can be applied to the groups —
- i.e. the value of the aggregate operator is no longer calculated over
+ aggregate functions described above can be applied to the groups —
+ i.e. the value of the aggregate function is no longer calculated over
all the values of the specified column but over all values of a
- group. Thus the aggregate operator is evaluated separately for every
+ group. Thus the aggregate function is evaluated separately for every
group.
</para>
<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>
<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>
-<!-- $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>
<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>
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>
<para>
The first form of aggregate expression invokes the aggregate
- across all input rows for which the given expression yields a
- non-null value. (Actually, it is up to the aggregate function
+ across all input rows for which the given expression(s) yield
+ non-null values. (Actually, it is up to the aggregate function
whether to ignore null values or not — but all the standard ones do.)
The second form is the same as the first, since
<literal>ALL</literal> is the default. The third form invokes the
- aggregate for all distinct non-null values of the expression found
+ aggregate for all distinct non-null values of the expressions found
in the input rows. The last form invokes the aggregate once for
each input row regardless of null or non-null values; since no
particular input value is specified, it is generally only useful
- for the <function>count()</function> aggregate function.
+ for the <function>count(*)</function> aggregate function.
</para>
<para>
<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,
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">
-<!-- $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
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
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:
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
void
AggregateCreate(const char *aggName,
Oid aggNamespace,
- Oid aggBaseType,
+ Oid *aggArgTypes,
+ int numArgs,
List *aggtransfnName,
List *aggfinalfnName,
List *aggsortopName,
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;
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);
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")));
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,
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 */
Oid *true_oid_array;
FuncDetailCode fdresult;
AclResult aclresult;
+ int i;
+ bool allPolyArgs = true;
/*
* func_get_detail looks up the function in the catalogs, does
* 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,
* 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);
*
*
* 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
TypeName *baseType = NULL;
TypeName *transType = NULL;
char *initval = NULL;
- Oid baseTypeId;
+ Oid *aggArgTypes;
+ int numArgs;
Oid transTypeId;
ListCell *pl;
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.
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);
}
}
*/
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 */
/* 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 */
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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.)
* 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
* 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 $
*
*-------------------------------------------------------------------------
*/
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 */
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,
}
/*
- * 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.
*/
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)
{
/*
* 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;
/*
* 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
if (!peraggstate->transtypeByVal &&
DatumGetPointer(newVal) != DatumGetPointer(pergroupstate->transValue))
{
- if (!fcinfo.isnull)
+ if (!fcinfo->isnull)
{
MemoryContextSwitchTo(aggstate->aggcontext);
newVal = datumCopy(newVal,
}
pergroupstate->transValue = newVal;
- pergroupstate->transValueIsNull = fcinfo.isnull;
+ pergroupstate->transValueIsNull = fcinfo->isnull;
MemoryContextSwitchTo(oldContext);
}
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);
}
}
}
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
*/
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;
/*
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;
}
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;
*finalfnexpr;
Datum textInitVal;
int i;
+ ListCell *lc;
/* Planner should have assigned aggregate to correct level */
Assert(aggref->agglevelsup == 0);
/* 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),
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,
/*
* 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",
/* 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;
}
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
* 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 $
*
*-------------------------------------------------------------------------
*/
{
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);
*
*
* 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*
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);
*
*
* 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
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);
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
{
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);
/*
* 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);
{
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");
*
*
* 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
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,
/* 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);
}
/*
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;
}
/*
- * 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")));
/* 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;
Aggref *newnode;
FLATCOPY(newnode, aggref, Aggref);
- MUTATE(newnode->target, aggref->target, Expr *);
+ MUTATE(newnode->args, aggref->args, List *);
return (Node *) newnode;
}
break;
*
*
* 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
| 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.
*
* 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;
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
/*
* 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
*/
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")));
* (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).
*
* *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,
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
* 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,
/*
* 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,
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
*
* 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)
Oid argoids[FUNC_MAX_ARGS];
int argcount;
int i;
- ListCell *args_item;
+ ListCell *lc;
Oid oid;
HeapTuple ftup;
Form_pg_proc pform;
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);
{
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",
* 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"
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, ')');
}
* 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 $
*
*-------------------------------------------------------------------------
*/
int i_oid;
int i_aggname;
int i_aggnamespace;
- int i_aggbasetype;
+ int i_pronargs;
+ int i_proargtypes;
int i_rolname;
int i_aggacl;
/* 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 "
{
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 "
"(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 "
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");
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));
format_aggregate_signature(AggInfo *agginfo, Archive *fout, bool honor_quotes)
{
PQExpBufferData buf;
+ int j;
initPQExpBuffer(&buf);
if (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;
}
int i_aggsortop;
int i_aggtranstype;
int i_agginitval;
- int i_anybasetype;
- int i_fmtbasetype;
int i_convertok;
const char *aggtransfn;
const char *aggfinalfn;
"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 "
"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 "
"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",
"(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",
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);
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);
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",
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,
/*
* 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);
* 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 $
*
*-------------------------------------------------------------------------
*/
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
*
* 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"
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,
* 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 $
*
*-------------------------------------------------------------------------
*/
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 200607261
+#define CATALOG_VERSION_NO 200607271
#endif
* 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
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}" ));
*/
extern void AggregateCreate(const char *aggName,
Oid aggNamespace,
- Oid aggBaseType,
+ Oid *aggArgTypes,
+ int numArgs,
List *aggtransfnName,
List *aggfinalfnName,
List *aggsortopName,
* 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
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");
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_ ));
* 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 $
*
*-------------------------------------------------------------------------
*/
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;
* 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 $
*
*-------------------------------------------------------------------------
*/
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;
* 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 $
*
*-------------------------------------------------------------------------
*/
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,
9 | 100 | 4
(10 rows)
+-- user-defined aggregates
SELECT newavg(four) AS avg_1 FROM onek;
avg_1
--------------------
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
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';
-- 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
-- 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
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.
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)
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)
-- 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;
-- -------
-- 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
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,
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,
-- -------
-- 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
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,
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,
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');
a | {1,2,3}
(3 rows)
+select mysum2(f1, f1 + 1) from t;
+ mysum2
+--------
+ 38
+(1 row)
+
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
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';
-- 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
-- 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
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.
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.
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;
-- -------
-- 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 = '{}');
-- -------
-- 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 = '{}');
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');
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;