diff --git a/doc/src/sgml/ref/alter_aggregate.sgml b/doc/src/sgml/ref/alter_aggregate.sgml
index 571a50a502..aab5b2b695 100644
--- a/doc/src/sgml/ref/alter_aggregate.sgml
+++ b/doc/src/sgml/ref/alter_aggregate.sgml
@@ -21,9 +21,12 @@ PostgreSQL documentation
-ALTER AGGREGATE name ( argtype [ , ... ] ) RENAME TO new_name
-ALTER AGGREGATE name ( argtype [ , ... ] ) OWNER TO new_owner
-ALTER AGGREGATE name ( argtype [ , ... ] ) SET SCHEMA new_schema
+ALTER AGGREGATE name ( [ argmode ] [ arg_name ] arg_data_type [ , ... ] )
+ RENAME TO new_name
+ALTER AGGREGATE name ( [ argmode ] [ arg_name ] arg_data_type [ , ... ] )
+ OWNER TO new_owner
+ALTER AGGREGATE name ( [ argmode ] [ arg_name ] arg_data_type [ , ... ] )
+ SET SCHEMA new_schema
@@ -62,12 +65,36 @@ ALTER AGGREGATE name ( argtype
- argtype
+ argmode
+
+
+
+ The mode of an argument: IN> or VARIADIC>.
+ If omitted, the default is IN>.
+
+
+
+
+
+ arg_name
+
+
+
+ The name of an argument.
+ Note that ALTER AGGREGATE does not actually pay
+ any attention to argument names, since only the argument data
+ types are needed to determine the aggregate function's identity.
+
+
+
+
+
+ arg_data_type
An input data type on which the aggregate function operates.
To reference a zero-argument aggregate function, write *>
- in place of the list of input data types.
+ in place of the list of argument specifications.
diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml
index 2dbba0c0bb..a14fcb4868 100644
--- a/doc/src/sgml/ref/alter_extension.sgml
+++ b/doc/src/sgml/ref/alter_extension.sgml
@@ -30,7 +30,7 @@ ALTER EXTENSION name DROP where member_object is:
- AGGREGATE agg_name (agg_type [, ...] ) |
+ AGGREGATE agg_name ( [ argmode ] [ argname ] agg_type [, ...] ) |
CAST (source_type AS target_type) |
COLLATION object_name |
CONVERSION object_name |
@@ -179,7 +179,7 @@ ALTER EXTENSION name DROP
An input data type on which the aggregate function operates.
To reference a zero-argument aggregate function, write *>
- in place of the list of input data types.
+ in place of the list of argument specifications.
diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml
index e94dd4b8de..e55050042a 100644
--- a/doc/src/sgml/ref/comment.sgml
+++ b/doc/src/sgml/ref/comment.sgml
@@ -23,7 +23,7 @@ PostgreSQL documentation
COMMENT ON
{
- AGGREGATE agg_name (agg_type [, ...] ) |
+ AGGREGATE agg_name ( [ argmode ] [ argname ] agg_type [, ...] ) |
CAST (source_type AS target_type) |
COLLATION object_name |
COLUMN relation_name.column_name |
@@ -126,7 +126,7 @@ COMMENT ON
An input data type on which the aggregate function operates.
To reference a zero-argument aggregate function, write *>
- in place of the list of input data types.
+ in place of the list of argument specifications.
@@ -156,7 +156,7 @@ COMMENT ON
The mode of a function argument: IN>, OUT>,
INOUT>, or VARIADIC>.
If omitted, the default is IN>.
- Note that COMMENT ON FUNCTION does not actually pay
+ Note that COMMENT does not actually pay
any attention to OUT> arguments, since only the input
arguments are needed to determine the function's identity.
So it is sufficient to list the IN>, INOUT>,
@@ -170,7 +170,7 @@ COMMENT ON
The name of a function argument.
- Note that COMMENT ON FUNCTION does not actually pay
+ Note that COMMENT does not actually pay
any attention to argument names, since only the argument data
types are needed to determine the function's identity.
diff --git a/doc/src/sgml/ref/create_aggregate.sgml b/doc/src/sgml/ref/create_aggregate.sgml
index d5e4e272fc..2b35fa4d52 100644
--- a/doc/src/sgml/ref/create_aggregate.sgml
+++ b/doc/src/sgml/ref/create_aggregate.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
-CREATE AGGREGATE name ( input_data_type [ , ... ] ) (
+CREATE AGGREGATE name ( [ argmode ] [ arg_name ] arg_data_type [ , ... ] ) (
SFUNC = sfunc,
STYPE = state_data_type
[ , FINALFUNC = ffunc ]
@@ -118,7 +118,7 @@ CREATE AGGREGATE name (
Note that this behavior is only available when
state_data_type
is the same as the first
- input_data_type.
+ arg_data_type.
When these types are different, you must supply a nonnull initial
condition or use a nonstrict transition function.
@@ -187,12 +187,36 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
- input_data_type
+ argmode
+
+
+
+ The mode of an argument: IN> or VARIADIC>.
+ (Aggregate functions do not support OUT> arguments.)
+ If omitted, the default is IN>. Only the last argument
+ can be marked VARIADIC>.
+
+
+
+
+
+ arg_name
+
+
+
+ The name of an argument. This is currently only useful for
+ documentation purposes. If omitted, the argument has no name.
+
+
+
+
+
+ arg_data_type
An input data type on which this aggregate function operates.
To create a zero-argument aggregate function, write *>
- 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 count(*).)
@@ -205,8 +229,8 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
In the old syntax for CREATE AGGREGATE>, the input data type
is specified by a 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 basetype> as
+ only one input parameter. To define a zero-argument aggregate function
+ with this syntax, specify the basetype> as
"ANY"> (not *>).
diff --git a/doc/src/sgml/ref/drop_aggregate.sgml b/doc/src/sgml/ref/drop_aggregate.sgml
index 1ed152f6a0..06060fb4f7 100644
--- a/doc/src/sgml/ref/drop_aggregate.sgml
+++ b/doc/src/sgml/ref/drop_aggregate.sgml
@@ -21,7 +21,9 @@ PostgreSQL documentation
-DROP AGGREGATE [ IF EXISTS ] name ( argtype [ , ... ] ) [ CASCADE | RESTRICT ]
+DROP AGGREGATE [ IF EXISTS ]
+ name ( [ argmode ] [ arg_name ] arg_data_type [ , ... ] )
+ [ CASCADE | RESTRICT ]
@@ -60,12 +62,36 @@ DROP AGGREGATE [ IF EXISTS ] name (
- argtype
+ argmode
+
+
+
+ The mode of an argument: IN> or VARIADIC>.
+ If omitted, the default is IN>.
+
+
+
+
+
+ arg_name
+
+
+
+ The name of an argument.
+ Note that DROP AGGREGATE does not actually pay
+ any attention to argument names, since only the argument data
+ types are needed to determine the aggregate function's identity.
+
+
+
+
+
+ arg_data_type
An input data type on which the aggregate function operates.
To reference a zero-argument aggregate function, write *>
- in place of the list of input data types.
+ in place of the list of argument specifications.
diff --git a/doc/src/sgml/ref/security_label.sgml b/doc/src/sgml/ref/security_label.sgml
index 52cb1d16f4..76c131f94e 100644
--- a/doc/src/sgml/ref/security_label.sgml
+++ b/doc/src/sgml/ref/security_label.sgml
@@ -25,7 +25,7 @@ SECURITY LABEL [ FOR provider ] ON
{
TABLE object_name |
COLUMN table_name.column_name |
- AGGREGATE agg_name (agg_type [, ...] ) |
+ AGGREGATE agg_name ( [ argmode ] [ argname ] agg_type [, ...] ) |
DATABASE object_name |
DOMAIN object_name |
EVENT TRIGGER object_name |
@@ -107,12 +107,12 @@ SECURITY LABEL [ FOR provider ] ON
- arg_type
+ agg_type
An input data type on which the aggregate function operates.
To reference a zero-argument aggregate function, write *>
- in place of the list of input data types.
+ in place of the list of argument specifications.
@@ -125,7 +125,7 @@ SECURITY LABEL [ FOR provider ] ON
The mode of a function argument: IN>, OUT>,
INOUT>, or VARIADIC>.
If omitted, the default is IN>.
- Note that SECURITY LABEL ON FUNCTION does not actually
+ Note that SECURITY LABEL does not actually
pay any attention to OUT> arguments, since only the input
arguments are needed to determine the function's identity.
So it is sufficient to list the IN>, INOUT>,
@@ -140,7 +140,7 @@ SECURITY LABEL [ FOR provider ] ON
The name of a function argument.
- Note that SECURITY LABEL ON FUNCTION does not actually
+ Note that SECURITY LABEL does not actually
pay any attention to argument names, since only the argument data
types are needed to determine the function's identity.
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 803ed855c8..e3dbc4b5ea 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.
+
+
+
+ Named and mixed call notations can currently be used only with regular
+ functions, not with aggregate functions or window functions.
+
+
diff --git a/doc/src/sgml/xaggr.sgml b/doc/src/sgml/xaggr.sgml
index 1822f6d4ab..9ed7d99f7c 100644
--- a/doc/src/sgml/xaggr.sgml
+++ b/doc/src/sgml/xaggr.sgml
@@ -169,6 +169,42 @@ SELECT attrelid::regclass, array_accum(atttypid::regtype)
+
+ An aggregate function can be made to accept a varying number of arguments
+ by declaring its last argument as a VARIADIC> array, in much
+ the same fashion as for regular functions; see
+ . The aggregate's transition
+ function must have the same array type as its last argument. The
+ transition function typically would also be marked VARIADIC>,
+ but this is not strictly required.
+
+
+
+
+ Variadic aggregates are easily misused in connection with
+ the ORDER BY> option (see ),
+ 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 ORDER BY> is a sort key, not an argument to the
+ aggregate. For example, in
+
+SELECT myaggregate(a ORDER BY a, b, c) FROM ...
+
+ the parser will see this as a single aggregate function argument and
+ three sort keys. However, the user might have intended
+
+SELECT myaggregate(a, b, c ORDER BY a) FROM ...
+
+ If myaggregate> is variadic, both these calls could be
+ perfectly valid.
+
+
+
+ For the same reason, it's wise to think twice before creating aggregate
+ functions with the same names and different numbers of regular arguments.
+
+
+
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 480c17cd39..d9e961ebfa 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 4a03786210..78af092465 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 0a9facfbaa..ca754b47ff 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 7a0c2541cb..e02a6ffa8c 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 bbc53361d6..544ba989de 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 788907ec0b..65f3b984d6 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 496e31dd2f..4c9b05e1e4 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 cff4734d40..a2903f9252 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 aad63e58cf..d325bb3212 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 22e82ba146..a9812af7cf 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_clause create_as_target create_mv_target
%type createfunc_opt_item common_func_opt_item dostmt_opt_item
-%type func_arg func_arg_with_default table_func_column
+%type func_arg func_arg_with_default table_func_column aggr_arg
%type arg_class
%type 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 4e4e1cddd8..98cb58a7cc 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 1f02c9a575..2bd24c89c8 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 c9408970b1..b1023c4e88 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 2b005d6e97..37d66e18e8 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 e1ef55f69a..57320cc83a 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,14 +11442,28 @@ 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, "
+ "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 "
+ "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);
@@ -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 dad1d5ac0d..ed1c5fdabc 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 2e51039c24..9e46c55ed5 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 6fb10a9456..5ad6ea6e3d 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 fa9f41f88f..836c99e97e 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 a778951362..791853730b 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 8fa0ca7f54..b6d9dd37b0 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 7fa900578c..1af79e57e9 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 ad1459419f..6c7566f172 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 256b7196d0..515cd9daaa 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 5c0196f5cf..397edffd3a 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 84f9a4f1e0..3c7d330960 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 a3be0c1114..efcd70f03b 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