diff --git a/doc/src/sgml/ref/alter_aggregate.sgml b/doc/src/sgml/ref/alter_aggregate.sgml
index 571a50a..aab5b2b 100644
*** a/doc/src/sgml/ref/alter_aggregate.sgml
--- b/doc/src/sgml/ref/alter_aggregate.sgml
*************** PostgreSQL documentation
*** 21,29 ****
  
   <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>
  </synopsis>
   </refsynopsisdiv>
  
--- 21,32 ----
  
   <refsynopsisdiv>
  <synopsis>
! 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>
  
*************** ALTER AGGREGATE <replaceable>name</repla
*** 62,73 ****
     </varlistentry>
  
     <varlistentry>
!     <term><replaceable class="parameter">argtype</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.
       </para>
      </listitem>
     </varlistentry>
--- 65,100 ----
     </varlistentry>
  
     <varlistentry>
!     <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 argument specifications.
       </para>
      </listitem>
     </varlistentry>
diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml
index 2dbba0c..a14fcb4 100644
*** a/doc/src/sgml/ref/alter_extension.sgml
--- b/doc/src/sgml/ref/alter_extension.sgml
*************** ALTER EXTENSION <replaceable class="PARA
*** 30,36 ****
  
  <phrase>where <replaceable class="PARAMETER">member_object</replaceable> is:</phrase>
  
!   AGGREGATE <replaceable class="PARAMETER">agg_name</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> |
--- 30,36 ----
  
  <phrase>where <replaceable class="PARAMETER">member_object</replaceable> is:</phrase>
  
!   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> |
*************** ALTER EXTENSION <replaceable class="PARA
*** 179,185 ****
        <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.
        </para>
       </listitem>
      </varlistentry>
--- 179,185 ----
        <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 argument specifications.
        </para>
       </listitem>
      </varlistentry>
diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml
index e94dd4b..e550500 100644
*** a/doc/src/sgml/ref/comment.sgml
--- b/doc/src/sgml/ref/comment.sgml
*************** PostgreSQL documentation
*** 23,29 ****
  <synopsis>
  COMMENT ON
  {
!   AGGREGATE <replaceable class="PARAMETER">agg_name</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> |
--- 23,29 ----
  <synopsis>
  COMMENT ON
  {
!   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> |
*************** COMMENT ON
*** 126,132 ****
       <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.
       </para>
      </listitem>
     </varlistentry>
--- 126,132 ----
       <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 argument specifications.
       </para>
      </listitem>
     </varlistentry>
*************** COMMENT ON
*** 156,162 ****
        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
        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</>,
--- 156,162 ----
        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</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</>,
*************** COMMENT ON
*** 170,176 ****
      <listitem>
       <para>
        The name of a function argument.
!       Note that <command>COMMENT ON FUNCTION</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>
--- 170,176 ----
      <listitem>
       <para>
        The name of a function argument.
!       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 d5e4e27..2b35fa4 100644
*** a/doc/src/sgml/ref/create_aggregate.sgml
--- b/doc/src/sgml/ref/create_aggregate.sgml
*************** PostgreSQL documentation
*** 21,27 ****
  
   <refsynopsisdiv>
  <synopsis>
! CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> ( <replaceable class="PARAMETER">input_data_type</replaceable> [ , ... ] ) (
      SFUNC = <replaceable class="PARAMETER">sfunc</replaceable>,
      STYPE = <replaceable class="PARAMETER">state_data_type</replaceable>
      [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
--- 21,27 ----
  
   <refsynopsisdiv>
  <synopsis>
! 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> ]
*************** CREATE AGGREGATE <replaceable class="PAR
*** 118,124 ****
     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>.
     When these types are different, you must supply a nonnull initial
     condition or use a nonstrict transition function.
    </para>
--- 118,124 ----
     Note that this behavior is only available when
     <replaceable class="PARAMETER">state_data_type</replaceable>
     is the same as the first
!    <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>
*************** SELECT col FROM tab ORDER BY col USING s
*** 187,198 ****
     </varlistentry>
  
     <varlistentry>
!     <term><replaceable class="PARAMETER">input_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
        aggregate is <function>count(*)</function>.)
       </para>
      </listitem>
--- 187,222 ----
     </varlistentry>
  
     <varlistentry>
!     <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 argument specifications.  (An example of such an
        aggregate is <function>count(*)</function>.)
       </para>
      </listitem>
*************** SELECT col FROM tab ORDER BY col USING s
*** 205,212 ****
        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
        <literal>"ANY"</> (not <literal>*</>).
       </para>
      </listitem>
--- 229,236 ----
        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
!       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 1ed152f..06060fb 100644
*** a/doc/src/sgml/ref/drop_aggregate.sgml
--- b/doc/src/sgml/ref/drop_aggregate.sgml
*************** PostgreSQL documentation
*** 21,27 ****
  
   <refsynopsisdiv>
  <synopsis>
! DROP AGGREGATE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> ( <replaceable class="PARAMETER">argtype</replaceable> [ , ... ] ) [ CASCADE | RESTRICT ]
  </synopsis>
   </refsynopsisdiv>
  
--- 21,29 ----
  
   <refsynopsisdiv>
  <synopsis>
! 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>
  
*************** DROP AGGREGATE [ IF EXISTS ] <replaceabl
*** 60,71 ****
     </varlistentry>
  
     <varlistentry>
!     <term><replaceable class="parameter">argtype</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.
       </para>
      </listitem>
     </varlistentry>
--- 62,97 ----
     </varlistentry>
  
     <varlistentry>
!     <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 argument specifications.
       </para>
      </listitem>
     </varlistentry>
diff --git a/doc/src/sgml/ref/security_label.sgml b/doc/src/sgml/ref/security_label.sgml
index 52cb1d1..76c131f 100644
*** a/doc/src/sgml/ref/security_label.sgml
--- b/doc/src/sgml/ref/security_label.sgml
*************** SECURITY LABEL [ FOR <replaceable class=
*** 25,31 ****
  {
    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> [, ...] ) |
    DATABASE <replaceable class="PARAMETER">object_name</replaceable> |
    DOMAIN <replaceable class="PARAMETER">object_name</replaceable> |
    EVENT TRIGGER <replaceable class="PARAMETER">object_name</replaceable> |
--- 25,31 ----
  {
    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">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> |
*************** SECURITY LABEL [ FOR <replaceable class=
*** 107,118 ****
     </varlistentry>
  
     <varlistentry>
!     <term><replaceable class="parameter">arg_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.
       </para>
      </listitem>
     </varlistentry>
--- 107,118 ----
     </varlistentry>
  
     <varlistentry>
!     <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 argument specifications.
       </para>
      </listitem>
     </varlistentry>
*************** SECURITY LABEL [ FOR <replaceable class=
*** 125,131 ****
        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
        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</>,
--- 125,131 ----
        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</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</>,
*************** SECURITY LABEL [ FOR <replaceable class=
*** 140,146 ****
      <listitem>
       <para>
        The name of a function argument.
!       Note that <command>SECURITY LABEL ON FUNCTION</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>
--- 140,146 ----
      <listitem>
       <para>
        The name of a function argument.
!       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 803ed85..e3dbc4b 100644
*** a/doc/src/sgml/syntax.sgml
--- b/doc/src/sgml/syntax.sgml
*************** SELECT concat_lower_or_upper('Hello', 'W
*** 2524,2529 ****
--- 2524,2536 ----
      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 1822f6d..9ed7d99 100644
*** a/doc/src/sgml/xaggr.sgml
--- b/doc/src/sgml/xaggr.sgml
*************** SELECT attrelid::regclass, array_accum(a
*** 170,175 ****
--- 170,211 ----
    </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
     <function>AggCheckCallContext</>, for example:
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 480c17c..d9e961e 100644
*** a/src/backend/catalog/pg_aggregate.c
--- b/src/backend/catalog/pg_aggregate.c
*************** static Oid lookup_agg_function(List *fnN
*** 45,52 ****
  Oid
  AggregateCreate(const char *aggName,
  				Oid aggNamespace,
- 				Oid *aggArgTypes,
  				int numArgs,
  				List *aggtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
--- 45,56 ----
  Oid
  AggregateCreate(const char *aggName,
  				Oid aggNamespace,
  				int numArgs,
+ 				oidvector *parameterTypes,
+ 				Datum allParameterTypes,
+ 				Datum parameterModes,
+ 				Datum parameterNames,
+ 				List *parameterDefaults,
  				List *aggtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
*************** AggregateCreate(const char *aggName,
*** 61,66 ****
--- 65,71 ----
  	Oid			transfn;
  	Oid			finalfn = InvalidOid;	/* can be omitted */
  	Oid			sortop = InvalidOid;	/* can be omitted */
+ 	Oid		   *aggArgTypes = parameterTypes->values;
  	bool		hasPolyArg;
  	bool		hasInternalArg;
  	Oid			rettype;
*************** AggregateCreate(const char *aggName,
*** 244,255 ****
  							  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 */
  							  PointerGetDatum(NULL),	/* proconfig */
  							  1,	/* procost */
  							  0);		/* prorows */
--- 249,259 ----
  							  false,	/* isStrict (not needed for agg) */
  							  PROVOLATILE_IMMUTABLE,	/* volatility (not
  														 * needed for agg) */
! 							  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 4a03786..78af092 100644
*** a/src/backend/commands/aggregatecmds.c
--- b/src/backend/commands/aggregatecmds.c
***************
*** 45,54 ****
   *
   * "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).
   */
  Oid
! DefineAggregate(List *name, List *args, bool oldstyle, List *parameters)
  {
  	char	   *aggName;
  	Oid			aggNamespace;
--- 45,56 ----
   *
   * "oldstyle" signals the old (pre-8.2) style where the aggregate input type
   * is specified by a BASETYPE element in the parameters.  Otherwise,
!  * "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,
! 				const char *queryString)
  {
  	char	   *aggName;
  	Oid			aggNamespace;
*************** DefineAggregate(List *name, List *args, 
*** 59,66 ****
  	TypeName   *baseType = NULL;
  	TypeName   *transType = NULL;
  	char	   *initval = NULL;
- 	Oid		   *aggArgTypes;
  	int			numArgs;
  	Oid			transTypeId;
  	char		transTypeType;
  	ListCell   *pl;
--- 61,72 ----
  	TypeName   *baseType = NULL;
  	TypeName   *transType = NULL;
  	char	   *initval = NULL;
  	int			numArgs;
+ 	oidvector  *parameterTypes;
+ 	ArrayType  *allParameterTypes;
+ 	ArrayType  *parameterModes;
+ 	ArrayType  *parameterNames;
+ 	List	   *parameterDefaults;
  	Oid			transTypeId;
  	char		transTypeType;
  	ListCell   *pl;
*************** DefineAggregate(List *name, List *args, 
*** 131,136 ****
--- 137,144 ----
  		 * 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),
*************** DefineAggregate(List *name, List *args, 
*** 139,160 ****
  		if (pg_strcasecmp(TypeNameToString(baseType), "ANY") == 0)
  		{
  			numArgs = 0;
! 			aggArgTypes = NULL;
  		}
  		else
  		{
  			numArgs = 1;
- 			aggArgTypes = (Oid *) palloc(sizeof(Oid));
  			aggArgTypes[0] = typenameTypeId(NULL, baseType);
  		}
  	}
  	else
  	{
  		/*
! 		 * New style: args is a list of TypeNames (possibly zero of 'em).
  		 */
! 		ListCell   *lc;
! 		int			i = 0;
  
  		if (baseType != NULL)
  			ereport(ERROR,
--- 147,172 ----
  		if (pg_strcasecmp(TypeNameToString(baseType), "ANY") == 0)
  		{
  			numArgs = 0;
! 			aggArgTypes[0] = InvalidOid;
  		}
  		else
  		{
  			numArgs = 1;
  			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 FunctionParameters (possibly zero of
! 		 * 'em).  We share functioncmds.c's code for processing them.
  		 */
! 		Oid			requiredResultType;
  
  		if (baseType != NULL)
  			ereport(ERROR,
*************** DefineAggregate(List *name, List *args, 
*** 162,174 ****
  					 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);
! 		}
  	}
  
  	/*
--- 174,193 ----
  					 errmsg("basetype is redundant with aggregate input type specification")));
  
  		numArgs = list_length(args);
! 		interpret_function_parameter_list(args,
! 										  InvalidOid,
! 										  true, /* is an aggregate */
! 										  queryString,
! 										  &parameterTypes,
! 										  &allParameterTypes,
! 										  &parameterModes,
! 										  &parameterNames,
! 										  &parameterDefaults,
! 										  &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);
  	}
  
  	/*
*************** DefineAggregate(List *name, List *args, 
*** 219,226 ****
  	 */
  	return AggregateCreate(aggName,		/* aggregate name */
  						   aggNamespace,		/* namespace */
- 						   aggArgTypes, /* input data type(s) */
  						   numArgs,
  						   transfuncName,		/* step function name */
  						   finalfuncName,		/* final function name */
  						   sortoperatorName,	/* sort operator name */
--- 238,249 ----
  	 */
  	return AggregateCreate(aggName,		/* aggregate name */
  						   aggNamespace,		/* namespace */
  						   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 0a9facf..ca754b4 100644
*** a/src/backend/commands/functioncmds.c
--- b/src/backend/commands/functioncmds.c
*************** compute_return_type(TypeName *returnType
*** 88,94 ****
  
  	typtup = LookupTypeName(NULL, returnType, NULL);
  
- 
  	if (typtup)
  	{
  		if (!((Form_pg_type) GETSTRUCT(typtup))->typisdefined)
--- 88,93 ----
*************** compute_return_type(TypeName *returnType
*** 158,179 ****
  }
  
  /*
!  * Interpret the parameter list of the CREATE FUNCTION statement.
   *
   * 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)
  {
  	int			parameterCount = list_length(parameters);
  	Oid		   *inTypes;
--- 157,187 ----
  }
  
  /*
!  * 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.
   */
! 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;
*************** examine_parameter_list(List *parameters,
*** 223,228 ****
--- 231,242 ----
  							(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),
*************** examine_parameter_list(List *parameters,
*** 246,254 ****
  			aclcheck_error_type(aclresult, toid);
  
  		if (t->setof)
! 			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)
--- 260,275 ----
  			aclcheck_error_type(aclresult, toid);
  
  		if (t->setof)
! 		{
! 			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)
*************** CreateFunction(CreateFunctionStmt *stmt,
*** 890,902 ****
  	 * Convert remaining parameters of CREATE to form wanted by
  	 * ProcedureCreate.
  	 */
! 	examine_parameter_list(stmt->parameters, languageOid, queryString,
! 						   &parameterTypes,
! 						   &allParameterTypes,
! 						   &parameterModes,
! 						   &parameterNames,
! 						   &parameterDefaults,
! 						   &requiredResultType);
  
  	if (stmt->returnType)
  	{
--- 911,926 ----
  	 * Convert remaining parameters of CREATE to form wanted by
  	 * ProcedureCreate.
  	 */
! 	interpret_function_parameter_list(stmt->parameters,
! 									  languageOid,
! 									  false,	/* not an aggregate */
! 									  queryString,
! 									  &parameterTypes,
! 									  &allParameterTypes,
! 									  &parameterModes,
! 									  &parameterNames,
! 									  &parameterDefaults,
! 									  &requiredResultType);
  
  	if (stmt->returnType)
  	{
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 7a0c254..e02a6ff 100644
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1696,1701 ****
--- 1696,1702 ----
  		/* 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 bbc5336..544ba98 100644
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
*************** initialize_peragg(WindowAggState *winsta
*** 1817,1822 ****
--- 1817,1823 ----
  	/* 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 788907e..65f3b98 100644
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyAggref(const Aggref *from)
*** 1141,1146 ****
--- 1141,1147 ----
  	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 496e31d..4c9b05e 100644
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalAggref(const Aggref *a, const Aggr
*** 198,203 ****
--- 198,204 ----
  	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 cff4734..a2903f9 100644
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outAggref(StringInfo str, const Aggref 
*** 962,967 ****
--- 962,968 ----
  	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 aad63e5..d325bb3 100644
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readAggref(void)
*** 497,502 ****
--- 497,503 ----
  	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 22e82ba..a9812af 100644
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** static Node *makeRecursiveViewSelect(cha
*** 324,331 ****
  				reloptions opt_reloptions
  				OptWith opt_distinct opt_definition func_args func_args_list
  				func_args_with_defaults func_args_with_defaults_list
  				func_as createfunc_opt_list alterfunc_opt_list
! 				aggr_args 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
--- 324,332 ----
  				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
! 				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
*************** static Node *makeRecursiveViewSelect(cha
*** 352,358 ****
  %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_mode> arg_class
  %type <typnam>	func_return func_type
  
--- 353,359 ----
  %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 aggr_arg
  %type <fun_param_mode> arg_class
  %type <typnam>	func_return func_type
  
*************** AlterExtensionContentsStmt:
*** 3659,3665 ****
  					n->action = $4;
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $6;
! 					n->objargs = $7;
  					$$ = (Node *)n;
  				}
  			| ALTER EXTENSION name add_drop CAST '(' Typename AS Typename ')'
--- 3660,3666 ----
  					n->action = $4;
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $6;
! 					n->objargs = extractArgTypes($7);
  					$$ = (Node *)n;
  				}
  			| ALTER EXTENSION name add_drop CAST '(' Typename AS Typename ')'
*************** def_arg:	func_type						{ $$ = (Node *)$
*** 4760,4769 ****
  			| Sconst						{ $$ = (Node *)makeString($1); }
  		;
  
- aggr_args:	'(' type_list ')'						{ $$ = $2; }
- 			| '(' '*' ')'							{ $$ = NIL; }
- 		;
- 
  old_aggr_definition: '(' old_aggr_list ')'			{ $$ = $2; }
  		;
  
--- 4761,4766 ----
*************** CommentStmt:
*** 5242,5248 ****
  					CommentStmt *n = makeNode(CommentStmt);
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $4;
! 					n->objargs = $5;
  					n->comment = $7;
  					$$ = (Node *) n;
  				}
--- 5239,5245 ----
  					CommentStmt *n = makeNode(CommentStmt);
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $4;
! 					n->objargs = extractArgTypes($5);
  					n->comment = $7;
  					$$ = (Node *) n;
  				}
*************** SecLabelStmt:
*** 5408,5414 ****
  					n->provider = $3;
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $6;
! 					n->objargs = $7;
  					n->label = $9;
  					$$ = (Node *) n;
  				}
--- 5405,5411 ----
  					n->provider = $3;
  					n->objtype = OBJECT_AGGREGATE;
  					n->objname = $6;
! 					n->objargs = extractArgTypes($7);
  					n->label = $9;
  					$$ = (Node *) n;
  				}
*************** func_arg_with_default:
*** 6395,6400 ****
--- 6392,6419 ----
  				}
  		;
  
+ /* 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 */
*************** RemoveAggrStmt:
*** 6594,6600 ****
  					DropStmt *n = makeNode(DropStmt);
  					n->removeType = OBJECT_AGGREGATE;
  					n->objects = list_make1($3);
! 					n->arguments = list_make1($4);
  					n->behavior = $5;
  					n->missing_ok = false;
  					n->concurrent = false;
--- 6613,6619 ----
  					DropStmt *n = makeNode(DropStmt);
  					n->removeType = OBJECT_AGGREGATE;
  					n->objects = list_make1($3);
! 					n->arguments = list_make1(extractArgTypes($4));
  					n->behavior = $5;
  					n->missing_ok = false;
  					n->concurrent = false;
*************** RemoveAggrStmt:
*** 6605,6611 ****
  					DropStmt *n = makeNode(DropStmt);
  					n->removeType = OBJECT_AGGREGATE;
  					n->objects = list_make1($5);
! 					n->arguments = list_make1($6);
  					n->behavior = $7;
  					n->missing_ok = true;
  					n->concurrent = false;
--- 6624,6630 ----
  					DropStmt *n = makeNode(DropStmt);
  					n->removeType = OBJECT_AGGREGATE;
  					n->objects = list_make1($5);
! 					n->arguments = list_make1(extractArgTypes($6));
  					n->behavior = $7;
  					n->missing_ok = true;
  					n->concurrent = false;
*************** RenameStmt: ALTER AGGREGATE func_name ag
*** 6821,6827 ****
  					RenameStmt *n = makeNode(RenameStmt);
  					n->renameType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = $4;
  					n->newname = $7;
  					n->missing_ok = false;
  					$$ = (Node *)n;
--- 6840,6846 ----
  					RenameStmt *n = makeNode(RenameStmt);
  					n->renameType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes($4);
  					n->newname = $7;
  					n->missing_ok = false;
  					$$ = (Node *)n;
*************** AlterObjectSchemaStmt:
*** 7295,7301 ****
  					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
  					n->objectType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = $4;
  					n->newschema = $7;
  					n->missing_ok = false;
  					$$ = (Node *)n;
--- 7314,7320 ----
  					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
  					n->objectType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = extractArgTypes($4);
  					n->newschema = $7;
  					n->missing_ok = false;
  					$$ = (Node *)n;
*************** AlterOwnerStmt: ALTER AGGREGATE func_nam
*** 7524,7530 ****
  					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
  					n->objectType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					n->objarg = $4;
  					n->newowner = $7;
  					$$ = (Node *)n;
  				}
--- 7543,7549 ----
  					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
  					n->objectType = OBJECT_AGGREGATE;
  					n->object = $3;
! 					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 4e4e1cd..98cb58a 100644
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
*************** check_ungrouped_columns_walker(Node *nod
*** 965,970 ****
--- 965,971 ----
  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,
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 975,980 ****
--- 976,982 ----
  {
  	Param	   *argp;
  	List	   *args;
+ 	FuncExpr   *fexpr;
  	int			i;
  
  	/*
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 1005,1016 ****
  		args = lappend(args, argp);
  	}
  
! 	*transfnexpr = (Expr *) makeFuncExpr(transfn_oid,
! 										 agg_state_type,
! 										 args,
! 										 InvalidOid,
! 										 agg_input_collation,
! 										 COERCE_EXPLICIT_CALL);
  
  	/* see if we have a final function */
  	if (!OidIsValid(finalfn_oid))
--- 1007,1020 ----
  		args = lappend(args, argp);
  	}
  
! 	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 1f02c9a..2bd24c8 100644
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 385,391 ****
  	}
  
  	/*
! 	 * When function is called 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.
  	 */
--- 385,391 ----
  	}
  
  	/*
! 	 * 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.
  	 */
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 425,432 ****
  		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;
  		/* agglevelsup will be set by transformAggregateCall */
  		aggref->location = location;
  
--- 425,433 ----
  		aggref->aggtype = rettype;
  		/* aggcollid and inputcollid will be set by parse_collate.c */
  		/* args, aggorder, aggdistinct will be set by transformAggregateCall */
  		aggref->aggfilter = agg_filter;
+ 		aggref->aggstar = agg_star;
+ 		aggref->aggvariadic = func_variadic;
  		/* agglevelsup will be set by transformAggregateCall */
  		aggref->location = location;
  
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 448,457 ****
  					 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.
  		 */
  		if (argnames != NIL)
  			ereport(ERROR,
--- 449,461 ----
  					 parser_errposition(pstate, location)));
  
  		/*
! 		 * 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 c940897..b1023c4 100644
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
*************** ProcessUtilitySlow(Node *parsetree,
*** 1103,1109 ****
  					{
  						case OBJECT_AGGREGATE:
  							DefineAggregate(stmt->defnames, stmt->args,
! 											stmt->oldstyle, stmt->definition);
  							break;
  						case OBJECT_OPERATOR:
  							Assert(stmt->args == NIL);
--- 1103,1110 ----
  					{
  						case OBJECT_AGGREGATE:
  							DefineAggregate(stmt->defnames, stmt->args,
! 											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 2b005d6..37d66e1 100644
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
*************** get_agg_expr(Aggref *aggref, deparse_con
*** 7405,7410 ****
--- 7405,7411 ----
  	Oid			argtypes[FUNC_MAX_ARGS];
  	List	   *arglist;
  	int			nargs;
+ 	bool		use_variadic;
  	ListCell   *l;
  
  	/* Extract the regular arguments, ignoring resjunk stuff for the moment */
*************** get_agg_expr(Aggref *aggref, deparse_con
*** 7430,7442 ****
  	appendStringInfo(buf, "%s(%s",
  					 generate_function_name(aggref->aggfnoid, nargs,
  											NIL, argtypes,
! 											false, NULL),
  					 (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);
  	if (aggref->aggorder != NIL)
  	{
  		appendStringInfoString(buf, " ORDER BY ");
--- 7431,7456 ----
  	appendStringInfo(buf, "%s(%s",
  					 generate_function_name(aggref->aggfnoid, nargs,
  											NIL, argtypes,
! 											aggref->aggvariadic,
! 											&use_variadic),
  					 (aggref->aggdistinct != NIL) ? "DISTINCT " : "");
+ 
  	/* aggstar can be set only in zero-argument aggregates */
  	if (aggref->aggstar)
  		appendStringInfoChar(buf, '*');
  	else
! 	{
! 		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 ");
*************** generate_relation_name(Oid relid, List *
*** 8581,8587 ****
   *		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
   * 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
--- 8595,8601 ----
   *		types.	(Those matter because of ambiguous-function resolution rules.)
   *
   * If we're dealing with a potentially variadic function (in practice, this
!  * 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 e1ef55f..57320cc 100644
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** static void getTableData(TableInfo *tbli
*** 229,235 ****
  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_old(Archive *fout,
  							  FuncInfo *finfo, int nallargs,
  							  char **allargtypes,
--- 229,236 ----
  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,
! 									   bool is_agg);
  static char *format_function_arguments_old(Archive *fout,
  							  FuncInfo *finfo, int nallargs,
  							  char **allargtypes,
*************** dumpProcLang(Archive *fout, ProcLangInfo
*** 9365,9379 ****
   * 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.
   */
  static char *
! format_function_arguments(FuncInfo *finfo, char *funcargs)
  {
  	PQExpBufferData fn;
  
  	initPQExpBuffer(&fn);
! 	appendPQExpBuffer(&fn, "%s(%s)", fmtId(finfo->dobj.name), funcargs);
  	return fn.data;
  }
  
--- 9366,9385 ----
   * 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.  Note, however, that pg_get_function_arguments
!  * does not special-case zero-argument aggregates.
   */
  static char *
! format_function_arguments(FuncInfo *finfo, char *funcargs, bool is_agg)
  {
  	PQExpBufferData fn;
  
  	initPQExpBuffer(&fn);
! 	appendPQExpBuffer(&fn, "%s", fmtId(finfo->dobj.name));
! 	if (is_agg && finfo->nargs == 0)
! 		appendPQExpBuffer(&fn, "(*)");
! 	else
! 		appendPQExpBuffer(&fn, "(%s)", funcargs);
  	return fn.data;
  }
  
*************** dumpFunc(Archive *fout, FuncInfo *finfo)
*** 9804,9811 ****
  	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);
  	}
  	else
  	{
--- 9810,9817 ----
  	if (funcargs)
  	{
  		/* 8.4 or later; we rely on server-side code for most of the work */
! 		funcfullsig = format_function_arguments(finfo, funcargs, false);
! 		funcsig = format_function_arguments(finfo, funciargs, false);
  	}
  	else
  	{
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11405,11411 ****
  	PQExpBuffer delq;
  	PQExpBuffer labelq;
  	PQExpBuffer details;
! 	char	   *aggsig;
  	char	   *aggsig_tag;
  	PGresult   *res;
  	int			i_aggtransfn;
--- 11411,11418 ----
  	PQExpBuffer delq;
  	PQExpBuffer labelq;
  	PQExpBuffer details;
! 	char	   *aggsig;			/* identity signature */
! 	char	   *aggfullsig;		/* full signature */
  	char	   *aggsig_tag;
  	PGresult   *res;
  	int			i_aggtransfn;
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11435,11452 ****
  	selectSourceSchema(fout, agginfo->aggfn.dobj.namespace->dobj.name);
  
  	/* Get aggregate-specific details */
! 	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, "
--- 11442,11473 ----
  	selectSourceSchema(fout, agginfo->aggfn.dobj.namespace->dobj.name);
  
  	/* Get aggregate-specific details */
! 	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 "
+ 						  "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, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11499,11505 ****
  	agginitval = PQgetvalue(res, 0, i_agginitval);
  	convertok = (PQgetvalue(res, 0, i_convertok)[0] == 't');
  
! 	aggsig = format_aggregate_signature(agginfo, fout, true);
  	aggsig_tag = format_aggregate_signature(agginfo, fout, false);
  
  	if (!convertok)
--- 11520,11543 ----
  	agginitval = PQgetvalue(res, 0, i_agginitval);
  	convertok = (PQgetvalue(res, 0, i_convertok)[0] == 't');
  
! 	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)
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11559,11565 ****
  					  aggsig);
  
  	appendPQExpBuffer(q, "CREATE AGGREGATE %s (\n%s\n);\n",
! 					  aggsig, details->data);
  
  	appendPQExpBuffer(labelq, "AGGREGATE %s", aggsig);
  
--- 11597,11603 ----
  					  aggsig);
  
  	appendPQExpBuffer(q, "CREATE AGGREGATE %s (\n%s\n);\n",
! 					  aggfullsig, details->data);
  
  	appendPQExpBuffer(labelq, "AGGREGATE %s", aggsig);
  
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index dad1d5a..ed1c5fd 100644
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
*************** describeAggregates(const char *pattern, 
*** 72,78 ****
  					  gettext_noop("Name"),
  					  gettext_noop("Result data type"));
  
! 	if (pset.sversion >= 80200)
  		appendPQExpBuffer(&buf,
  						  "  CASE WHEN p.pronargs = 0\n"
  						  "    THEN CAST('*' AS pg_catalog.text)\n"
--- 72,85 ----
  					  gettext_noop("Name"),
  					  gettext_noop("Result data type"));
  
! 	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/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 6fb10a9..5ad6ea6 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 3175	json_agg_transfn	json
*** 240,247 ****
   */
  extern Oid AggregateCreate(const char *aggName,
  				Oid aggNamespace,
- 				Oid *aggArgTypes,
  				int numArgs,
  				List *aggtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
--- 240,251 ----
   */
  extern Oid AggregateCreate(const char *aggName,
  				Oid aggNamespace,
  				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 fa9f41f..836c99e 100644
*** a/src/include/commands/defrem.h
--- b/src/include/commands/defrem.h
***************
*** 15,20 ****
--- 15,21 ----
  #define DEFREM_H
  
  #include "nodes/parsenodes.h"
+ #include "utils/array.h"
  
  /* commands/dropcmds.c */
  extern void RemoveObjects(DropStmt *stmt);
*************** extern void IsThereFunctionInNamespace(c
*** 53,58 ****
--- 54,69 ----
  						   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);
*************** extern void RemoveOperatorById(Oid operO
*** 60,66 ****
  
  /* commands/aggregatecmds.c */
  extern Oid DefineAggregate(List *name, List *args, bool oldstyle,
! 				List *parameters);
  
  /* commands/opclasscmds.c */
  extern Oid	DefineOpClass(CreateOpClassStmt *stmt);
--- 71,77 ----
  
  /* commands/aggregatecmds.c */
  extern Oid DefineAggregate(List *name, List *args, bool oldstyle,
! 				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 a778951..7918537 100644
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
*************** typedef struct Aggref
*** 249,254 ****
--- 249,255 ----
  	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 8fa0ca7..b6d9dd3 100644
*** a/src/include/parser/parse_agg.h
--- b/src/include/parser/parse_agg.h
*************** extern void parseCheckAggregates(ParseSt
*** 25,30 ****
--- 25,31 ----
  
  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 7fa9005..1af79e5 100644
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
*************** select aggfns(distinct a,b,c order by a,
*** 1249,1251 ****
--- 1249,1264 ----
   {"(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 ad14594..6c7566f 100644
*** a/src/test/regress/expected/create_aggregate.out
--- b/src/test/regress/expected/create_aggregate.out
*************** create aggregate aggfns(integer,integer,
*** 59,61 ****
--- 59,68 ----
     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 256b719..515cd9d 100644
*** a/src/test/regress/expected/opr_sanity.out
--- b/src/test/regress/expected/opr_sanity.out
*************** ORDER BY 1, 2;
*** 843,848 ****
--- 843,850 ----
  -- 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
*************** ORDER BY 1;
*** 855,861 ****
   count("any") | count()
  (1 row)
  
! -- For the same reason, aggregates with default arguments are no good.
  SELECT oid, proname
  FROM pg_proc AS p
  WHERE proisagg AND proargdefaults IS NOT NULL;
--- 857,871 ----
   count("any") | count()
  (1 row)
  
! -- 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 5c0196f..397edff 100644
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
*************** select sum(unique1) FILTER (WHERE
*** 480,482 ****
--- 480,486 ----
  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 84f9a4f..3c7d330 100644
*** a/src/test/regress/sql/create_aggregate.sql
--- b/src/test/regress/sql/create_aggregate.sql
*************** create aggregate aggfns(integer,integer,
*** 71,73 ****
--- 71,82 ----
     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 a3be0c1..efcd70f 100644
*** a/src/test/regress/sql/opr_sanity.sql
--- b/src/test/regress/sql/opr_sanity.sql
*************** ORDER BY 1, 2;
*** 674,679 ****
--- 674,681 ----
  -- 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
*************** WHERE p1.oid < p2.oid AND p1.proname = p
*** 683,689 ****
      array_dims(p1.proargtypes) != array_dims(p2.proargtypes)
  ORDER BY 1;
  
! -- For the same reason, aggregates with default arguments are no good.
  
  SELECT oid, proname
  FROM pg_proc AS p
--- 685,697 ----
      array_dims(p1.proargtypes) != array_dims(p2.proargtypes)
  ORDER BY 1;
  
! -- 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
