diff --git a/doc/src/sgml/ref/alter_aggregate.sgml b/doc/src/sgml/ref/alter_aggregate.sgml index 571a50a502ec0b47261ea51d92d4d2a15534eb84..aab5b2b695ec9a2a5b870379398b0a7e5913d0c3 100644 --- a/doc/src/sgml/ref/alter_aggregate.sgml +++ b/doc/src/sgml/ref/alter_aggregate.sgml @@ -21,9 +21,12 @@ PostgreSQL documentation <refsynopsisdiv> <synopsis> -ALTER AGGREGATE <replaceable>name</replaceable> ( <replaceable>argtype</replaceable> [ , ... ] ) RENAME TO <replaceable>new_name</replaceable> -ALTER AGGREGATE <replaceable>name</replaceable> ( <replaceable>argtype</replaceable> [ , ... ] ) OWNER TO <replaceable>new_owner</replaceable> -ALTER AGGREGATE <replaceable>name</replaceable> ( <replaceable>argtype</replaceable> [ , ... ] ) SET SCHEMA <replaceable>new_schema</replaceable> +ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] ) + RENAME TO <replaceable>new_name</replaceable> +ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] ) + OWNER TO <replaceable>new_owner</replaceable> +ALTER AGGREGATE <replaceable>name</replaceable> ( [ <replaceable>argmode</replaceable> ] [ <replaceable>arg_name</replaceable> ] <replaceable>arg_data_type</replaceable> [ , ... ] ) + SET SCHEMA <replaceable>new_schema</replaceable> </synopsis> </refsynopsisdiv> @@ -62,12 +65,36 @@ ALTER AGGREGATE <replaceable>name</replaceable> ( <replaceable>argtype</replacea </varlistentry> <varlistentry> - <term><replaceable class="parameter">argtype</replaceable></term> + <term><replaceable class="parameter">argmode</replaceable></term> + + <listitem> + <para> + The mode of an argument: <literal>IN</> or <literal>VARIADIC</>. + If omitted, the default is <literal>IN</>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">arg_name</replaceable></term> + + <listitem> + <para> + The name of an argument. + Note that <command>ALTER AGGREGATE</command> does not actually pay + any attention to argument names, since only the argument data + types are needed to determine the aggregate function's identity. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">arg_data_type</replaceable></term> <listitem> <para> 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. + in place of the list of argument specifications. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml index 2dbba0c0bbb05f3e1c344e42c41241047178bad3..a14fcb4868365ec3759218ace826974806d27083 100644 --- a/doc/src/sgml/ref/alter_extension.sgml +++ b/doc/src/sgml/ref/alter_extension.sgml @@ -30,7 +30,7 @@ ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> DROP <replacea <phrase>where <replaceable class="PARAMETER">member_object</replaceable> is:</phrase> - AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> (<replaceable class="PARAMETER">agg_type</replaceable> [, ...] ) | + AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) | CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) | COLLATION <replaceable class="PARAMETER">object_name</replaceable> | CONVERSION <replaceable class="PARAMETER">object_name</replaceable> | @@ -179,7 +179,7 @@ ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> DROP <replacea <para> 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. + in place of the list of argument specifications. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml index e94dd4b8dedd49986b8c47ecd7d4ed6a89b808bc..e55050042a8261bf3160b8cf2b01fb44623d1ba1 100644 --- a/doc/src/sgml/ref/comment.sgml +++ b/doc/src/sgml/ref/comment.sgml @@ -23,7 +23,7 @@ PostgreSQL documentation <synopsis> COMMENT ON { - AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> (<replaceable class="PARAMETER">agg_type</replaceable> [, ...] ) | + AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) | CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) | COLLATION <replaceable class="PARAMETER">object_name</replaceable> | COLUMN <replaceable class="PARAMETER">relation_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> | @@ -126,7 +126,7 @@ COMMENT ON <para> 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. + in place of the list of argument specifications. </para> </listitem> </varlistentry> @@ -156,7 +156,7 @@ COMMENT ON The mode of a function argument: <literal>IN</>, <literal>OUT</>, <literal>INOUT</>, or <literal>VARIADIC</>. If omitted, the default is <literal>IN</>. - Note that <command>COMMENT ON FUNCTION</command> does not actually pay + Note that <command>COMMENT</command> does not actually pay any attention to <literal>OUT</> arguments, since only the input arguments are needed to determine the function's identity. So it is sufficient to list the <literal>IN</>, <literal>INOUT</>, @@ -170,7 +170,7 @@ COMMENT ON <listitem> <para> The name of a function argument. - Note that <command>COMMENT ON FUNCTION</command> does not actually pay + Note that <command>COMMENT</command> does not actually pay any attention to argument names, since only the argument data types are needed to determine the function's identity. </para> diff --git a/doc/src/sgml/ref/create_aggregate.sgml b/doc/src/sgml/ref/create_aggregate.sgml index d5e4e272fce532a9adcd39f95a7c54affad4f97f..2b35fa4d5226adccfe4d8a6c1e244a56d0ac084f 100644 --- a/doc/src/sgml/ref/create_aggregate.sgml +++ b/doc/src/sgml/ref/create_aggregate.sgml @@ -21,7 +21,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">argmode</replaceable> ] [ <replaceable class="parameter">arg_name</replaceable> ] <replaceable class="parameter">arg_data_type</replaceable> [ , ... ] ) ( SFUNC = <replaceable class="PARAMETER">sfunc</replaceable>, STYPE = <replaceable class="PARAMETER">state_data_type</replaceable> [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ] @@ -118,7 +118,7 @@ CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> ( Note that this behavior is only available when <replaceable class="PARAMETER">state_data_type</replaceable> is the same as the first - <replaceable class="PARAMETER">input_data_type</replaceable>. + <replaceable class="PARAMETER">arg_data_type</replaceable>. When these types are different, you must supply a nonnull initial condition or use a nonstrict transition function. </para> @@ -187,12 +187,36 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1; </varlistentry> <varlistentry> - <term><replaceable class="PARAMETER">input_data_type</replaceable></term> + <term><replaceable class="parameter">argmode</replaceable></term> + + <listitem> + <para> + The mode of an argument: <literal>IN</> or <literal>VARIADIC</>. + (Aggregate functions do not support <literal>OUT</> arguments.) + If omitted, the default is <literal>IN</>. Only the last argument + can be marked <literal>VARIADIC</>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">arg_name</replaceable></term> + + <listitem> + <para> + The name of an argument. This is currently only useful for + documentation purposes. If omitted, the argument has no name. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="PARAMETER">arg_data_type</replaceable></term> <listitem> <para> 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 + in place of the list of argument specifications. (An example of such an aggregate is <function>count(*)</function>.) </para> </listitem> @@ -205,8 +229,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 a zero-argument aggregate function, - specify the <literal>basetype</> as + only one input parameter. To define a zero-argument aggregate function + with this syntax, specify the <literal>basetype</> as <literal>"ANY"</> (not <literal>*</>). </para> </listitem> diff --git a/doc/src/sgml/ref/drop_aggregate.sgml b/doc/src/sgml/ref/drop_aggregate.sgml index 1ed152f6a05646568737f59f2ba629d9f8d5638a..06060fb4f7bd02c428dbfca65090e6bf20ff9351 100644 --- a/doc/src/sgml/ref/drop_aggregate.sgml +++ b/doc/src/sgml/ref/drop_aggregate.sgml @@ -21,7 +21,9 @@ PostgreSQL documentation <refsynopsisdiv> <synopsis> -DROP AGGREGATE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> ( <replaceable class="PARAMETER">argtype</replaceable> [ , ... ] ) [ CASCADE | RESTRICT ] +DROP AGGREGATE [ IF EXISTS ] + <replaceable class="parameter">name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">arg_name</replaceable> ] <replaceable class="parameter">arg_data_type</replaceable> [ , ... ] ) + [ CASCADE | RESTRICT ] </synopsis> </refsynopsisdiv> @@ -60,12 +62,36 @@ DROP AGGREGATE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> ( </varlistentry> <varlistentry> - <term><replaceable class="parameter">argtype</replaceable></term> + <term><replaceable class="parameter">argmode</replaceable></term> + + <listitem> + <para> + The mode of an argument: <literal>IN</> or <literal>VARIADIC</>. + If omitted, the default is <literal>IN</>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">arg_name</replaceable></term> + + <listitem> + <para> + The name of an argument. + Note that <command>DROP AGGREGATE</command> does not actually pay + any attention to argument names, since only the argument data + types are needed to determine the aggregate function's identity. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">arg_data_type</replaceable></term> <listitem> <para> 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. + in place of the list of argument specifications. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/security_label.sgml b/doc/src/sgml/ref/security_label.sgml index 52cb1d16f4cc5ec962e9dd84da9b0d9c6343d3a4..76c131f94ee1c74c68cee0e5549988dba8599d1d 100644 --- a/doc/src/sgml/ref/security_label.sgml +++ b/doc/src/sgml/ref/security_label.sgml @@ -25,7 +25,7 @@ SECURITY LABEL [ FOR <replaceable class="PARAMETER">provider</replaceable> ] ON { TABLE <replaceable class="PARAMETER">object_name</replaceable> | COLUMN <replaceable class="PARAMETER">table_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> | - AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> (<replaceable class="PARAMETER">agg_type</replaceable> [, ...] ) | + AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> ( [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">agg_type</replaceable> [, ...] ) | DATABASE <replaceable class="PARAMETER">object_name</replaceable> | DOMAIN <replaceable class="PARAMETER">object_name</replaceable> | EVENT TRIGGER <replaceable class="PARAMETER">object_name</replaceable> | @@ -107,12 +107,12 @@ SECURITY LABEL [ FOR <replaceable class="PARAMETER">provider</replaceable> ] ON </varlistentry> <varlistentry> - <term><replaceable class="parameter">arg_type</replaceable></term> + <term><replaceable class="parameter">agg_type</replaceable></term> <listitem> <para> 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. + in place of the list of argument specifications. </para> </listitem> </varlistentry> @@ -125,7 +125,7 @@ SECURITY LABEL [ FOR <replaceable class="PARAMETER">provider</replaceable> ] ON The mode of a function argument: <literal>IN</>, <literal>OUT</>, <literal>INOUT</>, or <literal>VARIADIC</>. If omitted, the default is <literal>IN</>. - Note that <command>SECURITY LABEL ON FUNCTION</command> does not actually + Note that <command>SECURITY LABEL</command> does not actually pay any attention to <literal>OUT</> arguments, since only the input arguments are needed to determine the function's identity. So it is sufficient to list the <literal>IN</>, <literal>INOUT</>, @@ -140,7 +140,7 @@ SECURITY LABEL [ FOR <replaceable class="PARAMETER">provider</replaceable> ] ON <listitem> <para> The name of a function argument. - Note that <command>SECURITY LABEL ON FUNCTION</command> does not actually + Note that <command>SECURITY LABEL</command> does not actually pay any attention to argument names, since only the argument data types are needed to determine the function's identity. </para> diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml index 803ed855c821f8b5d85848ee0e1ade5b90280335..e3dbc4b5ea596a40da01455bbb74717f5c983504 100644 --- a/doc/src/sgml/syntax.sgml +++ b/doc/src/sgml/syntax.sgml @@ -2524,6 +2524,13 @@ SELECT concat_lower_or_upper('Hello', 'World', uppercase := true); having numerous parameters that have default values, named or mixed notation can save a great deal of writing and reduce chances for error. </para> + + <note> + <para> + Named and mixed call notations can currently be used only with regular + functions, not with aggregate functions or window functions. + </para> + </note> </sect2> </sect1> diff --git a/doc/src/sgml/xaggr.sgml b/doc/src/sgml/xaggr.sgml index 1822f6d4abd026786735ebb836d120d6e5455da0..9ed7d99f7c0e11ccaef6c7e519773d38c791c8b9 100644 --- a/doc/src/sgml/xaggr.sgml +++ b/doc/src/sgml/xaggr.sgml @@ -169,6 +169,42 @@ SELECT attrelid::regclass, array_accum(atttypid::regtype) </programlisting> </para> + <para> + An aggregate function can be made to accept a varying number of arguments + by declaring its last argument as a <literal>VARIADIC</> array, in much + the same fashion as for regular functions; see + <xref linkend="xfunc-sql-variadic-functions">. The aggregate's transition + function must have the same array type as its last argument. The + transition function typically would also be marked <literal>VARIADIC</>, + but this is not strictly required. + </para> + + <note> + <para> + Variadic aggregates are easily misused in connection with + the <literal>ORDER BY</> option (see <xref linkend="syntax-aggregates">), + since the parser cannot tell whether the wrong number of actual arguments + have been given in such a combination. Keep in mind that everything to + the right of <literal>ORDER BY</> is a sort key, not an argument to the + aggregate. For example, in +<programlisting> +SELECT myaggregate(a ORDER BY a, b, c) FROM ... +</programlisting> + the parser will see this as a single aggregate function argument and + three sort keys. However, the user might have intended +<programlisting> +SELECT myaggregate(a, b, c ORDER BY a) FROM ... +</programlisting> + If <literal>myaggregate</> is variadic, both these calls could be + perfectly valid. + </para> + + <para> + For the same reason, it's wise to think twice before creating aggregate + functions with the same names and different numbers of regular arguments. + </para> + </note> + <para> A function written in C can detect that it is being called as an aggregate transition or final function by calling diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c index 480c17cd3908375cbb70d6dfeb6659ee0da2810c..d9e961ebfad668cc56232e8793d230c4044cf70f 100644 --- a/src/backend/catalog/pg_aggregate.c +++ b/src/backend/catalog/pg_aggregate.c @@ -45,8 +45,12 @@ static Oid lookup_agg_function(List *fnName, int nargs, Oid *input_types, Oid AggregateCreate(const char *aggName, Oid aggNamespace, - Oid *aggArgTypes, int numArgs, + oidvector *parameterTypes, + Datum allParameterTypes, + Datum parameterModes, + Datum parameterNames, + List *parameterDefaults, List *aggtransfnName, List *aggfinalfnName, List *aggsortopName, @@ -61,6 +65,7 @@ AggregateCreate(const char *aggName, Oid transfn; Oid finalfn = InvalidOid; /* can be omitted */ Oid sortop = InvalidOid; /* can be omitted */ + Oid *aggArgTypes = parameterTypes->values; bool hasPolyArg; bool hasInternalArg; Oid rettype; @@ -244,12 +249,11 @@ AggregateCreate(const char *aggName, false, /* isStrict (not needed for agg) */ PROVOLATILE_IMMUTABLE, /* volatility (not * needed for agg) */ - buildoidvector(aggArgTypes, - numArgs), /* paramTypes */ - PointerGetDatum(NULL), /* allParamTypes */ - PointerGetDatum(NULL), /* parameterModes */ - PointerGetDatum(NULL), /* parameterNames */ - NIL, /* parameterDefaults */ + parameterTypes, /* paramTypes */ + allParameterTypes, /* allParamTypes */ + parameterModes, /* parameterModes */ + parameterNames, /* parameterNames */ + parameterDefaults, /* parameterDefaults */ PointerGetDatum(NULL), /* proconfig */ 1, /* procost */ 0); /* prorows */ diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c index 4a03786210acd4287e45d391af23864b831c9144..78af09246547a7fd091b13509ae1e5ddf1c44102 100644 --- a/src/backend/commands/aggregatecmds.c +++ b/src/backend/commands/aggregatecmds.c @@ -45,10 +45,12 @@ * * "oldstyle" signals the old (pre-8.2) style where the aggregate input type * is specified by a BASETYPE element in the parameters. Otherwise, - * "args" defines the input type(s). + * "args" is a list of FunctionParameter structs defining the agg's arguments. + * "parameters" is a list of DefElem representing the agg's definition clauses. */ Oid -DefineAggregate(List *name, List *args, bool oldstyle, List *parameters) +DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, + const char *queryString) { char *aggName; Oid aggNamespace; @@ -59,8 +61,12 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters) TypeName *baseType = NULL; TypeName *transType = NULL; char *initval = NULL; - Oid *aggArgTypes; int numArgs; + oidvector *parameterTypes; + ArrayType *allParameterTypes; + ArrayType *parameterModes; + ArrayType *parameterNames; + List *parameterDefaults; Oid transTypeId; char transTypeType; ListCell *pl; @@ -131,6 +137,8 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters) * Historically we allowed the command to look like basetype = 'ANY' * so we must do a case-insensitive comparison for the name ANY. Ugh. */ + Oid aggArgTypes[1]; + if (baseType == NULL) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), @@ -139,22 +147,26 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters) if (pg_strcasecmp(TypeNameToString(baseType), "ANY") == 0) { numArgs = 0; - aggArgTypes = NULL; + aggArgTypes[0] = InvalidOid; } else { numArgs = 1; - aggArgTypes = (Oid *) palloc(sizeof(Oid)); aggArgTypes[0] = typenameTypeId(NULL, baseType); } + parameterTypes = buildoidvector(aggArgTypes, numArgs); + allParameterTypes = NULL; + parameterModes = NULL; + parameterNames = NULL; + parameterDefaults = NIL; } else { /* - * New style: args is a list of TypeNames (possibly zero of 'em). + * New style: args is a list of FunctionParameters (possibly zero of + * 'em). We share functioncmds.c's code for processing them. */ - ListCell *lc; - int i = 0; + Oid requiredResultType; if (baseType != NULL) ereport(ERROR, @@ -162,13 +174,20 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters) errmsg("basetype is redundant with aggregate input type specification"))); numArgs = list_length(args); - aggArgTypes = (Oid *) palloc(sizeof(Oid) * numArgs); - foreach(lc, args) - { - TypeName *curTypeName = (TypeName *) lfirst(lc); - - aggArgTypes[i++] = typenameTypeId(NULL, curTypeName); - } + interpret_function_parameter_list(args, + InvalidOid, + true, /* is an aggregate */ + queryString, + ¶meterTypes, + &allParameterTypes, + ¶meterModes, + ¶meterNames, + ¶meterDefaults, + &requiredResultType); + /* Parameter defaults are not currently allowed by the grammar */ + Assert(parameterDefaults == NIL); + /* There shouldn't have been any OUT parameters, either */ + Assert(requiredResultType == InvalidOid); } /* @@ -219,8 +238,12 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters) */ return AggregateCreate(aggName, /* aggregate name */ aggNamespace, /* namespace */ - aggArgTypes, /* input data type(s) */ numArgs, + parameterTypes, + PointerGetDatum(allParameterTypes), + PointerGetDatum(parameterModes), + PointerGetDatum(parameterNames), + parameterDefaults, transfuncName, /* step function name */ finalfuncName, /* final function name */ sortoperatorName, /* sort operator name */ diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index 0a9facfbaaa535e13d9e5879724858baacdaf50a..ca754b47ff9dfc566e4246d141c1f3610cb3a58d 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -88,7 +88,6 @@ compute_return_type(TypeName *returnType, Oid languageOid, typtup = LookupTypeName(NULL, returnType, NULL); - if (typtup) { if (!((Form_pg_type) GETSTRUCT(typtup))->typisdefined) @@ -158,22 +157,31 @@ compute_return_type(TypeName *returnType, Oid languageOid, } /* - * Interpret the parameter list of the CREATE FUNCTION statement. + * Interpret the function parameter list of a CREATE FUNCTION or + * CREATE AGGREGATE statement. + * + * Input parameters: + * parameters: list of FunctionParameter structs + * languageOid: OID of function language (InvalidOid if it's CREATE AGGREGATE) + * is_aggregate: needed only to determine error handling + * queryString: likewise, needed only for error handling * * Results are stored into output parameters. parameterTypes must always * be created, but the other arrays are set to NULL if not needed. * requiredResultType is set to InvalidOid if there are no OUT parameters, * else it is set to the OID of the implied result type. */ -static void -examine_parameter_list(List *parameters, Oid languageOid, - const char *queryString, - oidvector **parameterTypes, - ArrayType **allParameterTypes, - ArrayType **parameterModes, - ArrayType **parameterNames, - List **parameterDefaults, - Oid *requiredResultType) +void +interpret_function_parameter_list(List *parameters, + Oid languageOid, + bool is_aggregate, + const char *queryString, + oidvector **parameterTypes, + ArrayType **allParameterTypes, + ArrayType **parameterModes, + ArrayType **parameterNames, + List **parameterDefaults, + Oid *requiredResultType) { int parameterCount = list_length(parameters); Oid *inTypes; @@ -223,6 +231,12 @@ examine_parameter_list(List *parameters, Oid languageOid, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("SQL function cannot accept shell type %s", TypeNameToString(t)))); + /* We don't allow creating aggregates on shell types either */ + else if (is_aggregate) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("aggregate cannot accept shell type %s", + TypeNameToString(t)))); else ereport(NOTICE, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -246,9 +260,16 @@ examine_parameter_list(List *parameters, Oid languageOid, aclcheck_error_type(aclresult, toid); if (t->setof) - ereport(ERROR, - (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("functions cannot accept set arguments"))); + { + if (is_aggregate) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("aggregates cannot accept set arguments"))); + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("functions cannot accept set arguments"))); + } /* handle input parameters */ if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE) @@ -890,13 +911,16 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString) * Convert remaining parameters of CREATE to form wanted by * ProcedureCreate. */ - examine_parameter_list(stmt->parameters, languageOid, queryString, - ¶meterTypes, - &allParameterTypes, - ¶meterModes, - ¶meterNames, - ¶meterDefaults, - &requiredResultType); + interpret_function_parameter_list(stmt->parameters, + languageOid, + false, /* not an aggregate */ + queryString, + ¶meterTypes, + &allParameterTypes, + ¶meterModes, + ¶meterNames, + ¶meterDefaults, + &requiredResultType); if (stmt->returnType) { diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index 7a0c2541cbe9d22a83f397a3dbb4d8f0eeb2ea00..e02a6ffa8c3485ed82d46ff4b6a5d7d9002366b0 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -1696,6 +1696,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) /* build expression trees using actual argument & result types */ build_aggregate_fnexprs(inputTypes, numArguments, + aggref->aggvariadic, aggtranstype, aggref->aggtype, aggref->inputcollid, diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c index bbc53361d639d2e0112ca890eacf6b9b425fb046..544ba989de126911a5871029233bbb27f375367b 100644 --- a/src/backend/executor/nodeWindowAgg.c +++ b/src/backend/executor/nodeWindowAgg.c @@ -1817,6 +1817,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc, /* build expression trees using actual argument & result types */ build_aggregate_fnexprs(inputTypes, numArguments, + false, /* no variadic window functions yet */ aggtranstype, wfunc->wintype, wfunc->inputcollid, diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 788907ec0b547d88855870811296f4846bf87199..65f3b984d647a5ff308b66ae4d3d8403f0a045f5 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1141,6 +1141,7 @@ _copyAggref(const Aggref *from) COPY_NODE_FIELD(aggdistinct); COPY_NODE_FIELD(aggfilter); COPY_SCALAR_FIELD(aggstar); + COPY_SCALAR_FIELD(aggvariadic); COPY_SCALAR_FIELD(agglevelsup); COPY_LOCATION_FIELD(location); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 496e31dd2f83bc19c6bdf11d4f51f9a0a1fd6e00..4c9b05e1e4ddfe29036c9597f99a47bd940fcf22 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -198,6 +198,7 @@ _equalAggref(const Aggref *a, const Aggref *b) COMPARE_NODE_FIELD(aggdistinct); COMPARE_NODE_FIELD(aggfilter); COMPARE_SCALAR_FIELD(aggstar); + COMPARE_SCALAR_FIELD(aggvariadic); COMPARE_SCALAR_FIELD(agglevelsup); COMPARE_LOCATION_FIELD(location); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index cff4734d40e0fd5f81edbdf738bbba1d85e8713a..a2903f92520987b34c06f167603c88e2570cf572 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -962,6 +962,7 @@ _outAggref(StringInfo str, const Aggref *node) WRITE_NODE_FIELD(aggdistinct); WRITE_NODE_FIELD(aggfilter); WRITE_BOOL_FIELD(aggstar); + WRITE_BOOL_FIELD(aggvariadic); WRITE_UINT_FIELD(agglevelsup); WRITE_LOCATION_FIELD(location); } diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index aad63e58cfdc9fa54882806e01cf7c48a1696e82..d325bb321295e8306394e5b131cc780d754f974d 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -497,6 +497,7 @@ _readAggref(void) READ_NODE_FIELD(aggdistinct); READ_NODE_FIELD(aggfilter); READ_BOOL_FIELD(aggstar); + READ_BOOL_FIELD(aggvariadic); READ_UINT_FIELD(agglevelsup); READ_LOCATION_FIELD(location); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 22e82ba146b19f8943c8aeaa0cabd59835a1a9be..a9812af7cfc422dfc583a77e9a215435eb90c930 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -324,8 +324,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); reloptions opt_reloptions OptWith opt_distinct opt_definition func_args func_args_list func_args_with_defaults func_args_with_defaults_list + aggr_args aggr_args_list func_as createfunc_opt_list alterfunc_opt_list - aggr_args old_aggr_definition old_aggr_list + old_aggr_definition old_aggr_list oper_argtypes RuleActionList RuleActionMulti opt_column_list columnList opt_name_list sort_clause opt_sort_clause sortby_list index_params @@ -352,7 +353,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <into> into_clause create_as_target create_mv_target %type <defelt> createfunc_opt_item common_func_opt_item dostmt_opt_item -%type <fun_param> func_arg func_arg_with_default table_func_column +%type <fun_param> func_arg func_arg_with_default table_func_column aggr_arg %type <fun_param_mode> arg_class %type <typnam> func_return func_type @@ -3659,7 +3660,7 @@ AlterExtensionContentsStmt: n->action = $4; n->objtype = OBJECT_AGGREGATE; n->objname = $6; - n->objargs = $7; + n->objargs = extractArgTypes($7); $$ = (Node *)n; } | ALTER EXTENSION name add_drop CAST '(' Typename AS Typename ')' @@ -4760,10 +4761,6 @@ def_arg: func_type { $$ = (Node *)$1; } | Sconst { $$ = (Node *)makeString($1); } ; -aggr_args: '(' type_list ')' { $$ = $2; } - | '(' '*' ')' { $$ = NIL; } - ; - old_aggr_definition: '(' old_aggr_list ')' { $$ = $2; } ; @@ -5242,7 +5239,7 @@ CommentStmt: CommentStmt *n = makeNode(CommentStmt); n->objtype = OBJECT_AGGREGATE; n->objname = $4; - n->objargs = $5; + n->objargs = extractArgTypes($5); n->comment = $7; $$ = (Node *) n; } @@ -5408,7 +5405,7 @@ SecLabelStmt: n->provider = $3; n->objtype = OBJECT_AGGREGATE; n->objname = $6; - n->objargs = $7; + n->objargs = extractArgTypes($7); n->label = $9; $$ = (Node *) n; } @@ -6395,6 +6392,28 @@ func_arg_with_default: } ; +/* Aggregate args can be most things that function args can be */ +aggr_arg: func_arg + { + if (!($1->mode == FUNC_PARAM_IN || + $1->mode == FUNC_PARAM_VARIADIC)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("aggregates cannot have output arguments"), + parser_errposition(@1))); + $$ = $1; + } + ; + +/* Zero-argument aggregates are named with * for consistency with COUNT(*) */ +aggr_args: '(' aggr_args_list ')' { $$ = $2; } + | '(' '*' ')' { $$ = NIL; } + ; + +aggr_args_list: + aggr_arg { $$ = list_make1($1); } + | aggr_args_list ',' aggr_arg { $$ = lappend($1, $3); } + ; createfunc_opt_list: /* Must be at least one to prevent conflict */ @@ -6594,7 +6613,7 @@ RemoveAggrStmt: DropStmt *n = makeNode(DropStmt); n->removeType = OBJECT_AGGREGATE; n->objects = list_make1($3); - n->arguments = list_make1($4); + n->arguments = list_make1(extractArgTypes($4)); n->behavior = $5; n->missing_ok = false; n->concurrent = false; @@ -6605,7 +6624,7 @@ RemoveAggrStmt: DropStmt *n = makeNode(DropStmt); n->removeType = OBJECT_AGGREGATE; n->objects = list_make1($5); - n->arguments = list_make1($6); + n->arguments = list_make1(extractArgTypes($6)); n->behavior = $7; n->missing_ok = true; n->concurrent = false; @@ -6821,7 +6840,7 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name RenameStmt *n = makeNode(RenameStmt); n->renameType = OBJECT_AGGREGATE; n->object = $3; - n->objarg = $4; + n->objarg = extractArgTypes($4); n->newname = $7; n->missing_ok = false; $$ = (Node *)n; @@ -7295,7 +7314,7 @@ AlterObjectSchemaStmt: AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt); n->objectType = OBJECT_AGGREGATE; n->object = $3; - n->objarg = $4; + n->objarg = extractArgTypes($4); n->newschema = $7; n->missing_ok = false; $$ = (Node *)n; @@ -7524,7 +7543,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId AlterOwnerStmt *n = makeNode(AlterOwnerStmt); n->objectType = OBJECT_AGGREGATE; n->object = $3; - n->objarg = $4; + n->objarg = extractArgTypes($4); n->newowner = $7; $$ = (Node *)n; } diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 4e4e1cddd839459063913006d48d393f53491096..98cb58a7cc0196078d385134d786baa07872e35d 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -965,6 +965,7 @@ check_ungrouped_columns_walker(Node *node, void build_aggregate_fnexprs(Oid *agg_input_types, int agg_num_inputs, + bool agg_variadic, Oid agg_state_type, Oid agg_result_type, Oid agg_input_collation, @@ -975,6 +976,7 @@ build_aggregate_fnexprs(Oid *agg_input_types, { Param *argp; List *args; + FuncExpr *fexpr; int i; /* @@ -1005,12 +1007,14 @@ build_aggregate_fnexprs(Oid *agg_input_types, args = lappend(args, argp); } - *transfnexpr = (Expr *) makeFuncExpr(transfn_oid, - agg_state_type, - args, - InvalidOid, - agg_input_collation, - COERCE_EXPLICIT_CALL); + fexpr = makeFuncExpr(transfn_oid, + agg_state_type, + args, + InvalidOid, + agg_input_collation, + COERCE_EXPLICIT_CALL); + fexpr->funcvariadic = agg_variadic; + *transfnexpr = (Expr *) fexpr; /* see if we have a final function */ if (!OidIsValid(finalfn_oid)) diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index 1f02c9a57572cca5026309ca028d3520ec0358f4..2bd24c89c87ee4ef6db5b05f56e348131e236ad0 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -385,7 +385,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, } /* - * When function is called an explicit VARIADIC labeled parameter, + * When function is called with an explicit VARIADIC labeled parameter, * and the declared_arg_type is "any", then sanity check the actual * parameter type now - it must be an array. */ @@ -425,8 +425,9 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, aggref->aggtype = rettype; /* aggcollid and inputcollid will be set by parse_collate.c */ /* args, aggorder, aggdistinct will be set by transformAggregateCall */ - aggref->aggstar = agg_star; aggref->aggfilter = agg_filter; + aggref->aggstar = agg_star; + aggref->aggvariadic = func_variadic; /* agglevelsup will be set by transformAggregateCall */ aggref->location = location; @@ -448,10 +449,13 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, parser_errposition(pstate, location))); /* - * Currently it's not possible to define an aggregate with named - * arguments, so this case should be impossible. Check anyway because - * the planner and executor wouldn't cope with NamedArgExprs in an - * Aggref node. + * We might want to support named arguments later, but disallow it for + * now. We'd need to figure out the parsed representation (should the + * NamedArgExprs go above or below the TargetEntry nodes?) and then + * teach the planner to reorder the list properly. Or maybe we could + * make transformAggregateCall do that? However, if you'd also like + * to allow default arguments for aggregates, we'd need to do it in + * planning to avoid semantic problems. */ if (argnames != NIL) ereport(ERROR, diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index c9408970b10a31a3f75cf863716a7393b16167c2..b1023c4e88221edaaae4b9d76c338c9d88cc0f1d 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1103,7 +1103,8 @@ ProcessUtilitySlow(Node *parsetree, { case OBJECT_AGGREGATE: DefineAggregate(stmt->defnames, stmt->args, - stmt->oldstyle, stmt->definition); + stmt->oldstyle, stmt->definition, + queryString); break; case OBJECT_OPERATOR: Assert(stmt->args == NIL); diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 2b005d6e973f93326965ad1a75d0b558d2419006..37d66e18e8f4d19ec04478330e29b7632ee47624 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -7405,6 +7405,7 @@ get_agg_expr(Aggref *aggref, deparse_context *context) Oid argtypes[FUNC_MAX_ARGS]; List *arglist; int nargs; + bool use_variadic; ListCell *l; /* Extract the regular arguments, ignoring resjunk stuff for the moment */ @@ -7430,13 +7431,26 @@ get_agg_expr(Aggref *aggref, deparse_context *context) appendStringInfo(buf, "%s(%s", generate_function_name(aggref->aggfnoid, nargs, NIL, argtypes, - false, NULL), + aggref->aggvariadic, + &use_variadic), (aggref->aggdistinct != NIL) ? "DISTINCT " : ""); + /* aggstar can be set only in zero-argument aggregates */ if (aggref->aggstar) appendStringInfoChar(buf, '*'); else - get_rule_expr((Node *) arglist, context, true); + { + nargs = 0; + foreach(l, arglist) + { + if (nargs++ > 0) + appendStringInfoString(buf, ", "); + if (use_variadic && lnext(l) == NULL) + appendStringInfoString(buf, "VARIADIC "); + get_rule_expr((Node *) lfirst(l), context, true); + } + } + if (aggref->aggorder != NIL) { appendStringInfoString(buf, " ORDER BY "); @@ -8581,7 +8595,7 @@ generate_relation_name(Oid relid, List *namespaces) * types. (Those matter because of ambiguous-function resolution rules.) * * If we're dealing with a potentially variadic function (in practice, this - * means a FuncExpr and not some other way of calling the function), then + * means a FuncExpr or Aggref, not some other way of calling a function), then * was_variadic must specify whether VARIADIC appeared in the original call, * and *use_variadic_p will be set to indicate whether to print VARIADIC in * the output. For non-FuncExpr cases, was_variadic should be FALSE and diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index e1ef55f69a8af58b0af968f4bc64cb5ab8e8d383..57320cc83a6fe625f3b07bfa0ebf50f529b052cc 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -229,7 +229,8 @@ static void getTableData(TableInfo *tblinfo, int numTables, bool oids); static void makeTableDataInfo(TableInfo *tbinfo, bool oids); static void buildMatViewRefreshDependencies(Archive *fout); static void getTableDataFKConstraints(void); -static char *format_function_arguments(FuncInfo *finfo, char *funcargs); +static char *format_function_arguments(FuncInfo *finfo, char *funcargs, + bool is_agg); static char *format_function_arguments_old(Archive *fout, FuncInfo *finfo, int nallargs, char **allargtypes, @@ -9365,15 +9366,20 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang) * format_function_arguments: generate function name and argument list * * This is used when we can rely on pg_get_function_arguments to format - * the argument list. + * the argument list. Note, however, that pg_get_function_arguments + * does not special-case zero-argument aggregates. */ static char * -format_function_arguments(FuncInfo *finfo, char *funcargs) +format_function_arguments(FuncInfo *finfo, char *funcargs, bool is_agg) { PQExpBufferData fn; initPQExpBuffer(&fn); - appendPQExpBuffer(&fn, "%s(%s)", fmtId(finfo->dobj.name), funcargs); + appendPQExpBuffer(&fn, "%s", fmtId(finfo->dobj.name)); + if (is_agg && finfo->nargs == 0) + appendPQExpBuffer(&fn, "(*)"); + else + appendPQExpBuffer(&fn, "(%s)", funcargs); return fn.data; } @@ -9804,8 +9810,8 @@ dumpFunc(Archive *fout, FuncInfo *finfo) if (funcargs) { /* 8.4 or later; we rely on server-side code for most of the work */ - funcfullsig = format_function_arguments(finfo, funcargs); - funcsig = format_function_arguments(finfo, funciargs); + funcfullsig = format_function_arguments(finfo, funcargs, false); + funcsig = format_function_arguments(finfo, funciargs, false); } else { @@ -11405,7 +11411,8 @@ dumpAgg(Archive *fout, AggInfo *agginfo) PQExpBuffer delq; PQExpBuffer labelq; PQExpBuffer details; - char *aggsig; + char *aggsig; /* identity signature */ + char *aggfullsig; /* full signature */ char *aggsig_tag; PGresult *res; int i_aggtransfn; @@ -11435,18 +11442,32 @@ dumpAgg(Archive *fout, AggInfo *agginfo) selectSourceSchema(fout, agginfo->aggfn.dobj.namespace->dobj.name); /* Get aggregate-specific details */ - if (fout->remoteVersion >= 80100) + if (fout->remoteVersion >= 80400) { appendPQExpBuffer(query, "SELECT aggtransfn, " "aggfinalfn, aggtranstype::pg_catalog.regtype, " "aggsortop::pg_catalog.regoperator, " "agginitval, " - "'t'::boolean AS convertok " + "'t'::boolean AS convertok, " + "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs, " + "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs " "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p " "WHERE a.aggfnoid = p.oid " "AND p.oid = '%u'::pg_catalog.oid", agginfo->aggfn.dobj.catId.oid); } + else if (fout->remoteVersion >= 80100) + { + appendPQExpBuffer(query, "SELECT aggtransfn, " + "aggfinalfn, aggtranstype::pg_catalog.regtype, " + "aggsortop::pg_catalog.regoperator, " + "agginitval, " + "'t'::boolean AS convertok " + "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p " + "WHERE a.aggfnoid = p.oid " + "AND p.oid = '%u'::pg_catalog.oid", + agginfo->aggfn.dobj.catId.oid); + } else if (fout->remoteVersion >= 70300) { appendPQExpBuffer(query, "SELECT aggtransfn, " @@ -11499,7 +11520,24 @@ dumpAgg(Archive *fout, AggInfo *agginfo) agginitval = PQgetvalue(res, 0, i_agginitval); convertok = (PQgetvalue(res, 0, i_convertok)[0] == 't'); - aggsig = format_aggregate_signature(agginfo, fout, true); + if (fout->remoteVersion >= 80400) + { + /* 8.4 or later; we rely on server-side code for most of the work */ + char *funcargs; + char *funciargs; + + funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs")); + funciargs = PQgetvalue(res, 0, PQfnumber(res, "funciargs")); + aggfullsig = format_function_arguments(&agginfo->aggfn, funcargs, true); + aggsig = format_function_arguments(&agginfo->aggfn, funciargs, true); + } + else + { + /* pre-8.4, do it ourselves */ + aggsig = format_aggregate_signature(agginfo, fout, true); + aggfullsig = aggsig; + } + aggsig_tag = format_aggregate_signature(agginfo, fout, false); if (!convertok) @@ -11559,7 +11597,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo) aggsig); appendPQExpBuffer(q, "CREATE AGGREGATE %s (\n%s\n);\n", - aggsig, details->data); + aggfullsig, details->data); appendPQExpBuffer(labelq, "AGGREGATE %s", aggsig); diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index dad1d5ac0de7e2667148e61248b90e13a00fd4fb..ed1c5fdabc81b516ff79ed8e93c3e44cbaecbf06 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -72,7 +72,14 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem) gettext_noop("Name"), gettext_noop("Result data type")); - if (pset.sversion >= 80200) + if (pset.sversion >= 80400) + appendPQExpBuffer(&buf, + " CASE WHEN p.pronargs = 0\n" + " THEN CAST('*' AS pg_catalog.text)\n" + " ELSE pg_catalog.pg_get_function_arguments(p.oid)\n" + " END AS \"%s\",\n", + gettext_noop("Argument data types")); + else if (pset.sversion >= 80200) appendPQExpBuffer(&buf, " CASE WHEN p.pronargs = 0\n" " THEN CAST('*' AS pg_catalog.text)\n" diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 2e51039c24b1e99751cf36269f0b994cb0441a8f..9e46c55ed5614969dc97b86ccbc459443c8e8367 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201307221 +#define CATALOG_VERSION_NO 201309031 #endif diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h index 6fb10a945672e455506773e2db1bc274552c070d..5ad6ea6e3d1a9c89372069bb9334a04b91f7f626 100644 --- a/src/include/catalog/pg_aggregate.h +++ b/src/include/catalog/pg_aggregate.h @@ -240,8 +240,12 @@ DATA(insert ( 3175 json_agg_transfn json_agg_finalfn 0 2281 _null_ )); */ extern Oid AggregateCreate(const char *aggName, Oid aggNamespace, - Oid *aggArgTypes, int numArgs, + oidvector *parameterTypes, + Datum allParameterTypes, + Datum parameterModes, + Datum parameterNames, + List *parameterDefaults, List *aggtransfnName, List *aggfinalfnName, List *aggsortopName, diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index fa9f41f88f0a5fa090121d68f169f624053c6165..836c99e97ea5127131491bfe77a2a0dbdf87c8de 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -15,6 +15,7 @@ #define DEFREM_H #include "nodes/parsenodes.h" +#include "utils/array.h" /* commands/dropcmds.c */ extern void RemoveObjects(DropStmt *stmt); @@ -53,6 +54,16 @@ extern void IsThereFunctionInNamespace(const char *proname, int pronargs, oidvector *proargtypes, Oid nspOid); extern void ExecuteDoStmt(DoStmt *stmt); extern Oid get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok); +extern void interpret_function_parameter_list(List *parameters, + Oid languageOid, + bool is_aggregate, + const char *queryString, + oidvector **parameterTypes, + ArrayType **allParameterTypes, + ArrayType **parameterModes, + ArrayType **parameterNames, + List **parameterDefaults, + Oid *requiredResultType); /* commands/operatorcmds.c */ extern Oid DefineOperator(List *names, List *parameters); @@ -60,7 +71,7 @@ extern void RemoveOperatorById(Oid operOid); /* commands/aggregatecmds.c */ extern Oid DefineAggregate(List *name, List *args, bool oldstyle, - List *parameters); + List *parameters, const char *queryString); /* commands/opclasscmds.c */ extern Oid DefineOpClass(CreateOpClassStmt *stmt); diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index a778951362d09cf388fb6b6146e956c5b38b7d4f..791853730b3bc7b19f3748f807bcb44bd9999969 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -249,6 +249,7 @@ typedef struct Aggref List *aggdistinct; /* DISTINCT (list of SortGroupClause) */ Expr *aggfilter; /* FILTER expression */ bool aggstar; /* TRUE if argument list was really '*' */ + bool aggvariadic; /* TRUE if VARIADIC was used in call */ Index agglevelsup; /* > 0 if agg belongs to outer query */ int location; /* token location, or -1 if unknown */ } Aggref; diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h index 8fa0ca7f54db7aacc3b3bd1b3e0b26976853475b..b6d9dd37b04c3b5e1428c581dc72fbce21201171 100644 --- a/src/include/parser/parse_agg.h +++ b/src/include/parser/parse_agg.h @@ -25,6 +25,7 @@ extern void parseCheckAggregates(ParseState *pstate, Query *qry); extern void build_aggregate_fnexprs(Oid *agg_input_types, int agg_num_inputs, + bool agg_variadic, Oid agg_state_type, Oid agg_result_type, Oid agg_input_collation, diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out index 7fa900578c7170b1e5d3d7a3b620230d4b79753e..1af79e57e92f11441a8398fc0559d00e208055fa 100644 --- a/src/test/regress/expected/aggregates.out +++ b/src/test/regress/expected/aggregates.out @@ -1249,3 +1249,16 @@ select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1) {"(2,2,bar)","(3,1,baz)"} (1 row) +-- variadic aggregates +select least_agg(q1,q2) from int8_tbl; + least_agg +------------------- + -4567890123456789 +(1 row) + +select least_agg(variadic array[q1,q2]) from int8_tbl; + least_agg +------------------- + -4567890123456789 +(1 row) + diff --git a/src/test/regress/expected/create_aggregate.out b/src/test/regress/expected/create_aggregate.out index ad1459419f883a7b5fa0316e6ab62b81fab34977..6c7566f1728fed6a2547870ba608be64a320bc45 100644 --- a/src/test/regress/expected/create_aggregate.out +++ b/src/test/regress/expected/create_aggregate.out @@ -59,3 +59,10 @@ create aggregate aggfns(integer,integer,text) ( sfunc = aggfns_trans, stype = aggtype[], initcond = '{}' ); +-- variadic aggregate +create function least_accum(anyelement, variadic anyarray) +returns anyelement language sql as + 'select least($1, min($2[i])) from generate_subscripts($2,1) g(i)'; +create aggregate least_agg(variadic items anyarray) ( + stype = anyelement, sfunc = least_accum +); diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 256b7196d0d67ae0406af84c353483b6765e7538..515cd9daaa8f5ebf6714d9db6232ebe4e6a9cee7 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -843,6 +843,8 @@ ORDER BY 1, 2; -- to avoid this because it opens the door for confusion in connection with -- ORDER BY: novices frequently put the ORDER BY in the wrong place. -- See the fate of the single-argument form of string_agg() for history. +-- (Note: we don't forbid users from creating such aggregates; the policy is +-- just to think twice before creating built-in aggregates like this.) -- The only aggregates that should show up here are count(x) and count(*). SELECT p1.oid::regprocedure, p2.oid::regprocedure FROM pg_proc AS p1, pg_proc AS p2 @@ -855,7 +857,15 @@ ORDER BY 1; count("any") | count() (1 row) --- For the same reason, aggregates with default arguments are no good. +-- For the same reason, we avoid creating built-in variadic aggregates. +SELECT oid, proname +FROM pg_proc AS p +WHERE proisagg AND provariadic != 0; + oid | proname +-----+--------- +(0 rows) + +-- For the same reason, built-in aggregates with default arguments are no good. SELECT oid, proname FROM pg_proc AS p WHERE proisagg AND proargdefaults IS NOT NULL; diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql index 5c0196f5cf9e34cac24a2f468493669b2344ca6a..397edffd3ada13af2b3986466cd23f1559d3aa3e 100644 --- a/src/test/regress/sql/aggregates.sql +++ b/src/test/regress/sql/aggregates.sql @@ -480,3 +480,7 @@ select sum(unique1) FILTER (WHERE select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1) from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), generate_series(1,2) i; + +-- variadic aggregates +select least_agg(q1,q2) from int8_tbl; +select least_agg(variadic array[q1,q2]) from int8_tbl; diff --git a/src/test/regress/sql/create_aggregate.sql b/src/test/regress/sql/create_aggregate.sql index 84f9a4f1e0fc3a3a717ff7d17c2e544379c6cedf..3c7d330960ff5f6723e8f1c82c79f61791c7a49c 100644 --- a/src/test/regress/sql/create_aggregate.sql +++ b/src/test/regress/sql/create_aggregate.sql @@ -71,3 +71,12 @@ create aggregate aggfns(integer,integer,text) ( sfunc = aggfns_trans, stype = aggtype[], initcond = '{}' ); + +-- variadic aggregate +create function least_accum(anyelement, variadic anyarray) +returns anyelement language sql as + 'select least($1, min($2[i])) from generate_subscripts($2,1) g(i)'; + +create aggregate least_agg(variadic items anyarray) ( + stype = anyelement, sfunc = least_accum +); diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql index a3be0c1114f4d67212c65780d3e42f35584fda28..efcd70f03be89a9a49210e722ee3d807a8cc3e1b 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -674,6 +674,8 @@ ORDER BY 1, 2; -- to avoid this because it opens the door for confusion in connection with -- ORDER BY: novices frequently put the ORDER BY in the wrong place. -- See the fate of the single-argument form of string_agg() for history. +-- (Note: we don't forbid users from creating such aggregates; the policy is +-- just to think twice before creating built-in aggregates like this.) -- The only aggregates that should show up here are count(x) and count(*). SELECT p1.oid::regprocedure, p2.oid::regprocedure @@ -683,7 +685,13 @@ WHERE p1.oid < p2.oid AND p1.proname = p2.proname AND array_dims(p1.proargtypes) != array_dims(p2.proargtypes) ORDER BY 1; --- For the same reason, aggregates with default arguments are no good. +-- For the same reason, we avoid creating built-in variadic aggregates. + +SELECT oid, proname +FROM pg_proc AS p +WHERE proisagg AND provariadic != 0; + +-- For the same reason, built-in aggregates with default arguments are no good. SELECT oid, proname FROM pg_proc AS p