*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 2034,2039 ****
--- 2034,2049 ----
     </table>
  
     <para>
+    Functions <function>concat</function>, <function>concat_ws</function> and
+    <function>format</function> are variadic - variadic arguments should be passed 
+    as usual or as array that is labeled as <literal>VARIADIC</literal>.
+    Functions <function>concat</function> and <function>concat_ws</function>
+    returns NULL, when variadic array argument is NULL and empty string
+    when variadic array argument is empty array. Result of <function>format</function>
+    primary depends on content of fmt string.
+    </para>
+ 
+    <para>
     See also the aggregate function <function>string_agg</function> in
     <xref linkend="functions-aggregate">.
     </para>
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
***************
*** 76,83 **** static bytea *bytea_substring(Datum str,
  				bool length_not_specified);
  static bytea *bytea_overlay(bytea *t1, bytea *t2, int sp, int sl);
  static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
! void text_format_string_conversion(StringInfo buf, char conversion,
! 							  Oid typid, Datum value, bool isNull);
  
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
--- 76,83 ----
  				bool length_not_specified);
  static bytea *bytea_overlay(bytea *t1, bytea *t2, int sp, int sl);
  static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
! static void text_format_string_conversion(StringInfo buf, char conversion,
! 							  Oid typOutput, Datum value, bool isNull);
  
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
***************
*** 3792,3839 **** string_agg_finalfn(PG_FUNCTION_ARGS)
   * Implementation of both concat() and concat_ws().
   *
   * sepstr/seplen describe the separator.  argidx is the first argument
!  * to concatenate (counting from zero).
   */
  static text *
! concat_internal(const char *sepstr, int seplen, int argidx,
  				FunctionCallInfo fcinfo)
  {
! 	text	   *result;
! 	StringInfoData str;
! 	bool		first_arg = true;
! 	int			i;
  
! 	initStringInfo(&str);
  
! 	for (i = argidx; i < PG_NARGS(); i++)
  	{
! 		if (!PG_ARGISNULL(i))
  		{
! 			Datum		value = PG_GETARG_DATUM(i);
! 			Oid			valtype;
! 			Oid			typOutput;
! 			bool		typIsVarlena;
! 
! 			/* add separator if appropriate */
! 			if (first_arg)
! 				first_arg = false;
! 			else
! 				appendBinaryStringInfo(&str, sepstr, seplen);
! 
! 			/* call the appropriate type output function, append the result */
! 			valtype = get_fn_expr_argtype(fcinfo->flinfo, i);
! 			if (!OidIsValid(valtype))
! 				elog(ERROR, "could not determine data type of concat() input");
! 			getTypeOutputInfo(valtype, &typOutput, &typIsVarlena);
! 			appendStringInfoString(&str,
! 								   OidOutputFunctionCall(typOutput, value));
  		}
- 	}
  
! 	result = cstring_to_text_with_len(str.data, str.len);
! 	pfree(str.data);
  
! 	return result;
  }
  
  /*
--- 3792,3877 ----
   * Implementation of both concat() and concat_ws().
   *
   * sepstr/seplen describe the separator.  argidx is the first argument
!  * to concatenate (counting from zero). When argument with variadic
!  * array is NULL, then returns NULL, else string or empty string.
   */
  static text *
! concat_internal(char *sepstr, int argidx,
  				FunctionCallInfo fcinfo)
  {
! 	/*
! 	 * When this routine is called from variadic function with
! 	 * labeled VARIADIC parameter, then sematic is very close to
! 	 * array_to_string function - and we can use code
! 	 * array_to_text_internal, that is well optimized.
! 	 */
! 	if (get_fn_expr_variadic(fcinfo->flinfo))
! 	{
! 		Oid		 arr_typid;
! 		ArrayType	*arr;
  
! 		/* do nothing, when argument is NULL */
! 		if (fcinfo->argnull[fcinfo->nargs - 1])
! 			return NULL;
! 
! 		/* sanity check - any array type is requested */
! 		arr_typid = get_fn_expr_argtype(fcinfo->flinfo, fcinfo->nargs - 1);
! 		if (!OidIsValid(arr_typid))
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
! 					 errmsg("could not determinate variadic argument data type")));
! 
! 		if (!OidIsValid(get_element_type(arr_typid)))
! 			ereport(ERROR,
! 					(errcode(ERRCODE_DATATYPE_MISMATCH),
! 					 errmsg("variadic argument should be an array")));
  
! 		/* now is safe get the array */
! 		arr = DatumGetArrayTypeP(fcinfo->arg[fcinfo->nargs - 1]);
! 
! 		/* serialize an array */
! 		return array_to_text_internal(fcinfo, arr, sepstr, NULL);
! 	}
! 	else
  	{
! 		text	   *result;
! 		StringInfoData str;
! 		bool		first_arg = true;
! 		int			i;
! 
! 		/* process variadic "any" parameters stored as FunctionCallInfo arguments */
! 		initStringInfo(&str);
! 
! 		for (i = argidx; i < PG_NARGS(); i++)
  		{
! 			if (!PG_ARGISNULL(i))
! 			{
! 				Datum		value = PG_GETARG_DATUM(i);
! 				Oid			valtype;
! 				Oid			typOutput;
! 				bool		typIsVarlena;
! 
! 				/* add separator if appropriate */
! 				if (first_arg)
! 					first_arg = false;
! 				else
! 					appendStringInfoString(&str, sepstr);
! 
! 				/* call the appropriate type output function, append the result */
! 				valtype = get_fn_expr_argtype(fcinfo->flinfo, i);
! 				if (!OidIsValid(valtype))
! 					elog(ERROR, "could not determine data type of concat() input");
! 				getTypeOutputInfo(valtype, &typOutput, &typIsVarlena);
! 				appendStringInfoString(&str,
! 									   OidOutputFunctionCall(typOutput, value));
! 			}
  		}
  
! 		result = cstring_to_text_with_len(str.data, str.len);
! 		pfree(str.data);
  
! 		return result;
! 	}
  }
  
  /*
***************
*** 3842,3848 **** concat_internal(const char *sepstr, int seplen, int argidx,
  Datum
  text_concat(PG_FUNCTION_ARGS)
  {
! 	PG_RETURN_TEXT_P(concat_internal("", 0, 0, fcinfo));
  }
  
  /*
--- 3880,3892 ----
  Datum
  text_concat(PG_FUNCTION_ARGS)
  {
! 	text	   *result;
! 
! 	result = concat_internal("", 0, fcinfo);
! 	if (result == NULL)
! 		PG_RETURN_NULL();
! 
! 	PG_RETURN_TEXT_P(result);
  }
  
  /*
***************
*** 3852,3867 **** text_concat(PG_FUNCTION_ARGS)
  Datum
  text_concat_ws(PG_FUNCTION_ARGS)
  {
! 	text	   *sep;
  
  	/* return NULL when separator is NULL */
  	if (PG_ARGISNULL(0))
  		PG_RETURN_NULL();
  
! 	sep = PG_GETARG_TEXT_PP(0);
  
! 	PG_RETURN_TEXT_P(concat_internal(VARDATA_ANY(sep), VARSIZE_ANY_EXHDR(sep),
! 									 1, fcinfo));
  }
  
  /*
--- 3896,3915 ----
  Datum
  text_concat_ws(PG_FUNCTION_ARGS)
  {
! 	char	   *sep;
! 	text	   *result;
  
  	/* return NULL when separator is NULL */
  	if (PG_ARGISNULL(0))
  		PG_RETURN_NULL();
  
! 	sep = text_to_cstring(PG_GETARG_TEXT_PP(0));
! 	result = concat_internal(sep, 1, fcinfo);
  
! 	if (result == NULL)
! 		PG_RETURN_NULL();
! 
! 	PG_RETURN_TEXT_P(result);
  }
  
  /*
***************
*** 3959,3969 **** text_format(PG_FUNCTION_ARGS)
--- 4007,4079 ----
  	const char *end_ptr;
  	text	   *result;
  	int			arg = 0;
+ 	bool		funcvariadic;
+ 	int		nargs;
+ 	Datum		*elements = NULL;
+ 	bool		*nulls = NULL;
+ 	Oid		   element_type = InvalidOid;
+ 	Oid		   prev_type = InvalidOid;
+ 	Oid		   typoutputfunc = InvalidOid;
  
  	/* When format string is null, returns null */
  	if (PG_ARGISNULL(0))
  		PG_RETURN_NULL();
  
+ 	/* when last argument was marked as VARIADIC sanitize variadic argument */
+ 	if (get_fn_expr_variadic(fcinfo->flinfo))
+ 	{
+ 		Oid		   array_type;
+ 		ArrayType	   *arr;
+ 		int16		typlen;
+ 		bool		typbyval;
+ 		char		typalign;
+ 		char		typdelim;
+ 		Oid		typioparam;
+ 		int		nitems = 0;
+ 
+ 		/*
+ 		 * Variadic argument should be NULL in this case, becase there should not
+ 		 * be any reference from fmt string.
+ 		 */
+ 		if (!PG_ARGISNULL(1))
+ 		{
+ 			/* sanity check - any array type is requested */
+ 			array_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ 			if (!OidIsValid(array_type))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 						 errmsg("could not determinate variadic argument data type")));
+ 
+ 			if (!OidIsValid(get_element_type(array_type)))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						 errmsg("variadic argument should be an array")));
+ 
+ 			/* now we know so variadic "any" argument is really array */
+ 			arr = PG_GETARG_ARRAYTYPE_P(1);
+ 			element_type = ARR_ELEMTYPE(arr);
+ 
+ 			get_type_io_data(element_type, IOFunc_output,
+ 							 &typlen, &typbyval, &typalign,
+ 							 &typdelim, &typioparam, &typoutputfunc);
+ 
+ 			/* we know typoutput function, so we can set prev_typid */
+ 			prev_type = element_type;
+ 
+ 			deconstruct_array(arr, element_type, typlen, typbyval,
+ 							  typalign, &elements, &nulls,
+ 							  &nitems);
+ 		}
+ 
+ 		nargs = nitems + 1;
+ 		funcvariadic = true;
+ 	}
+ 	else
+ 	{
+ 		nargs = PG_NARGS();
+ 		funcvariadic = false;
+ 	}
+ 
  	/* Setup for main loop. */
  	fmt = PG_GETARG_TEXT_PP(0);
  	start_ptr = VARDATA_ANY(fmt);
***************
*** 4062,4068 **** text_format(PG_FUNCTION_ARGS)
  		}
  
  		/* Not enough arguments?  Deduct 1 to avoid counting format string. */
! 		if (arg > PG_NARGS() - 1)
  			ereport(ERROR,
  					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
  					 errmsg("too few arguments for format")));
--- 4172,4178 ----
  		}
  
  		/* Not enough arguments?  Deduct 1 to avoid counting format string. */
! 		if (arg > nargs - 1)
  			ereport(ERROR,
  					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
  					 errmsg("too few arguments for format")));
***************
*** 4072,4087 **** text_format(PG_FUNCTION_ARGS)
  		 * or not an argument position was present, it's known that at least
  		 * one character remains in the string at this point.
  		 */
! 		value = PG_GETARG_DATUM(arg);
! 		isNull = PG_ARGISNULL(arg);
! 		typid = get_fn_expr_argtype(fcinfo->flinfo, arg);
  
  		switch (*cp)
  		{
  			case 's':
  			case 'I':
  			case 'L':
! 				text_format_string_conversion(&str, *cp, typid, value, isNull);
  				break;
  			default:
  				ereport(ERROR,
--- 4182,4215 ----
  		 * or not an argument position was present, it's known that at least
  		 * one character remains in the string at this point.
  		 */
! 		if (!funcvariadic)
! 		{
! 			value = PG_GETARG_DATUM(arg);
! 			isNull = PG_ARGISNULL(arg);
! 			typid = get_fn_expr_argtype(fcinfo->flinfo, arg);
! 		}
! 		else
! 		{
! 			value = elements[arg - 1];
! 			isNull = nulls[arg - 1];
! 			typid = element_type;
! 		}
! 
! 		/* try to reuse typOutput function */
! 		if (typoutputfunc == InvalidOid || prev_type != typid)
! 		{
! 			bool	typIsVarlena;
! 
! 			getTypeOutputInfo(typid, &typoutputfunc, &typIsVarlena);
! 			prev_type = typid;
! 		}
  
  		switch (*cp)
  		{
  			case 's':
  			case 'I':
  			case 'L':
! 				text_format_string_conversion(&str, *cp, typoutputfunc, value, isNull);
  				break;
  			default:
  				ereport(ERROR,
***************
*** 4095,4110 **** text_format(PG_FUNCTION_ARGS)
  	result = cstring_to_text_with_len(str.data, str.len);
  	pfree(str.data);
  
  	PG_RETURN_TEXT_P(result);
  }
  
  /* Format a %s, %I, or %L conversion. */
! void
  text_format_string_conversion(StringInfo buf, char conversion,
! 							  Oid typid, Datum value, bool isNull)
  {
- 	Oid			typOutput;
- 	bool		typIsVarlena;
  	char	   *str;
  
  	/* Handle NULL arguments before trying to stringify the value. */
--- 4223,4241 ----
  	result = cstring_to_text_with_len(str.data, str.len);
  	pfree(str.data);
  
+ 	if (elements != NULL)
+ 		pfree(elements);
+ 	if (nulls != NULL)
+ 		pfree(nulls);
+ 
  	PG_RETURN_TEXT_P(result);
  }
  
  /* Format a %s, %I, or %L conversion. */
! static void
  text_format_string_conversion(StringInfo buf, char conversion,
! 							  Oid typOutput, Datum value, bool isNull)
  {
  	char	   *str;
  
  	/* Handle NULL arguments before trying to stringify the value. */
***************
*** 4120,4126 **** text_format_string_conversion(StringInfo buf, char conversion,
  	}
  
  	/* Stringify. */
- 	getTypeOutputInfo(typid, &typOutput, &typIsVarlena);
  	str = OidOutputFunctionCall(typOutput, value);
  
  	/* Escape. */
--- 4251,4256 ----
*** a/src/test/regress/expected/text.out
--- b/src/test/regress/expected/text.out
***************
*** 136,141 **** select quote_literal(e'\\');
--- 136,175 ----
   E'\\'
  (1 row)
  
+ -- check variadic labeled argument
+ select concat(variadic array[1,2,3]);
+  concat 
+ --------
+  123
+ (1 row)
+ 
+ select concat_ws(',', variadic array[1,2,3]);
+  concat_ws 
+ -----------
+  1,2,3
+ (1 row)
+ 
+ select concat_ws(',', variadic NULL);
+  concat_ws 
+ -----------
+  
+ (1 row)
+ 
+ select concat(variadic NULL) is NULL;
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ select concat(variadic '{}'::int[]) = '';
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ --should fail
+ select concat_ws(',', variadic 10);
+ ERROR:  variadic argument should be an array
  /*
   * format
   */
***************
*** 241,243 **** select format('Hello %s %s, %2$s %2$s', 'World', 'Hello again');
--- 275,330 ----
   Hello World Hello again, Hello again Hello again
  (1 row)
  
+ -- check pass variadic labeled argument
+ select format('%s, %s', variadic array['Hello','World']);
+     format    
+ --------------
+  Hello, World
+ (1 row)
+ 
+ -- check others datatypes
+ select format('%s, %s', variadic array[1, 2]);
+  format 
+ --------
+  1, 2
+ (1 row)
+ 
+ select format('%s, %s', variadic array[true, false]);
+  format 
+ --------
+  t, f
+ (1 row)
+ 
+ select format('%s, %s', variadic array[true, false]::text[]);
+    format    
+ -------------
+  true, false
+ (1 row)
+ 
+ -- check positional placeholders
+ select format('%2$s, %1$s', variadic array['first', 'second']);
+     format     
+ ---------------
+  second, first
+ (1 row)
+ 
+ select format('%2$s, %1$s', variadic array[1, 2]);
+  format 
+ --------
+  2, 1
+ (1 row)
+ 
+ -- variadic argument can be NULL, but should not be referenced
+ select format('Hello', variadic NULL);
+  format 
+ --------
+  Hello
+ (1 row)
+ 
+ -- check bypassing FUNC_MAX_ARGS limit via variadic labeled argument
+ select right(format(string_agg('%s',','), variadic array_agg(i)),31) from generate_series(1,500) g(i);
+               right              
+ ---------------------------------
+  493,494,495,496,497,498,499,500
+ (1 row)
+ 
*** a/src/test/regress/sql/text.sql
--- b/src/test/regress/sql/text.sql
***************
*** 44,49 **** select i, left('ahoj', i), right('ahoj', i) from generate_series(-5, 5) t(i) ord
--- 44,57 ----
  select quote_literal('');
  select quote_literal('abc''');
  select quote_literal(e'\\');
+ -- check variadic labeled argument
+ select concat(variadic array[1,2,3]);
+ select concat_ws(',', variadic array[1,2,3]);
+ select concat_ws(',', variadic NULL);
+ select concat(variadic NULL) is NULL;
+ select concat(variadic '{}'::int[]) = '';
+ --should fail
+ select concat_ws(',', variadic 10);
  
  /*
   * format
***************
*** 76,78 **** select format('%1$1', 1);
--- 84,103 ----
  --checkk mix of positional and ordered placeholders
  select format('Hello %s %1$s %s', 'World', 'Hello again');
  select format('Hello %s %s, %2$s %2$s', 'World', 'Hello again');
+ 
+ -- check pass variadic labeled argument
+ select format('%s, %s', variadic array['Hello','World']);
+ -- check others datatypes
+ select format('%s, %s', variadic array[1, 2]);
+ select format('%s, %s', variadic array[true, false]);
+ select format('%s, %s', variadic array[true, false]::text[]);
+ 
+ -- check positional placeholders
+ select format('%2$s, %1$s', variadic array['first', 'second']);
+ select format('%2$s, %1$s', variadic array[1, 2]);
+ 
+ -- variadic argument can be NULL, but should not be referenced
+ select format('Hello', variadic NULL);
+ 
+ -- check bypassing FUNC_MAX_ARGS limit via variadic labeled argument
+ select right(format(string_agg('%s',','), variadic array_agg(i)),31) from generate_series(1,500) g(i);
