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