*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 1538,1543 ****
--- 1538,1561 ----
        </row>
  
        <row>
+        <entry id="format_array">
+         <indexterm>
+          <primary>format_array</primary>
+         </indexterm>
+         <literal><function>format_array</function>(<parameter>formatstr</parameter> <type>text</type>
+         , <parameter>params</parameter> <type>text[]</type>)</literal>
+        </entry>
+        <entry><type>text</type></entry>
+        <entry>
+          Format a string.  This function is similar to function <function>format</function>.
+          It uses same formating rules. But this function is not variadic - parameters are
+          passed in array - second parameter of this function.
+        </entry>
+        <entry><literal>format_array('Hello %s, %1$s', array['World'])</literal></entry>
+        <entry><literal>Hello World, World</literal></entry>
+       </row>
+ 
+       <row>
         <entry>
          <indexterm>
           <primary>initcap</primary>
*** a/src/backend/catalog/namespace.c
--- b/src/backend/catalog/namespace.c
***************
*** 1048,1053 **** FuncnameGetCandidates(List *names, int nargs, List *argnames,
--- 1048,1061 ----
  			/* Ignore if it doesn't match requested argument count */
  			if (nargs >= 0 && pronargs != nargs && !variadic && !use_defaults)
  				continue;
+ 
+ 			/*
+ 			 * Ignore variadic "ANY" function with disabled expand_variadic.
+ 			 * There are no way, how to signalize target function about this
+ 			 * situation.
+ 			 */
+ 			if (!expand_variadic && procform->provariadic == ANYOID)
+ 				continue;
  		}
  
  		/*
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
***************
*** 50,55 **** typedef struct
--- 50,72 ----
  	int			skiptable[256]; /* skip distance for given mismatched char */
  } TextPositionState;
  
+ typedef struct
+ {
+ 	FunctionCallInfo fcinfo;
+ 	int			nargs;
+ } text_format_context;
+ 
+ typedef struct
+ {
+ 	int			nelems;
+ 	Datum		*elem_values;
+ 	bool		*elem_nulls;
+ 	Oid		typOutput;
+ } text_format_array_context;
+ 
+ typedef void (*param_feeder_type) (StringInfo str, const char conversion,
+ 							    int arg,  void *context);
+ 
  #define DatumGetUnknownP(X)			((unknown *) PG_DETOAST_DATUM(X))
  #define DatumGetUnknownPCopy(X)		((unknown *) PG_DETOAST_DATUM_COPY(X))
  #define PG_GETARG_UNKNOWN_P(n)		DatumGetUnknownP(PG_GETARG_DATUM(n))
***************
*** 76,87 **** 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,
  					   char *fldsep, char *null_string);
  
  
  /*****************************************************************************
--- 93,107 ----
  				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,
! 							  Datum value, bool isNull, Oid typOutput);
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   char *fldsep, char *null_string);
+ static void text_format_add_converted_arg(StringInfo str,
+ 						    const char conversion, int arg, void *context);
+ static text *text_format_internal(text *fmt, param_feeder_type param_feeder, void *context);
  
  
  /*****************************************************************************
***************
*** 3946,3958 **** text_reverse(PG_FUNCTION_ARGS)
  	PG_RETURN_TEXT_P(result);
  }
  
  /*
!  * Returns a formated string
   */
! Datum
! text_format(PG_FUNCTION_ARGS)
  {
- 	text	   *fmt;
  	StringInfoData str;
  	const char *cp;
  	const char *start_ptr;
--- 3966,3978 ----
  	PG_RETURN_TEXT_P(result);
  }
  
+ 
  /*
!  * generic function for format and format_array function
   */
! static text *
! text_format_internal(text *fmt, param_feeder_type param_feeder, void *context)
  {
  	StringInfoData str;
  	const char *cp;
  	const char *start_ptr;
***************
*** 3960,3971 **** text_format(PG_FUNCTION_ARGS)
  	text	   *result;
  	int			arg = 0;
  
- 	/* When format string is null, returns null */
- 	if (PG_ARGISNULL(0))
- 		PG_RETURN_NULL();
- 
  	/* Setup for main loop. */
- 	fmt = PG_GETARG_TEXT_PP(0);
  	start_ptr = VARDATA_ANY(fmt);
  	end_ptr = start_ptr + VARSIZE_ANY_EXHDR(fmt);
  	initStringInfo(&str);
--- 3980,3986 ----
***************
*** 3973,3982 **** text_format(PG_FUNCTION_ARGS)
  	/* Scan format string, looking for conversion specifiers. */
  	for (cp = start_ptr; cp < end_ptr; cp++)
  	{
- 		Datum		value;
- 		bool		isNull;
- 		Oid			typid;
- 
  		/*
  		 * If it's not the start of a conversion specifier, just copy it to
  		 * the output buffer.
--- 3988,3993 ----
***************
*** 4061,4087 **** text_format(PG_FUNCTION_ARGS)
  						 errmsg("conversion specifies argument 0, but arguments are numbered from 1")));
  		}
  
- 		/* 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")));
- 
- 		/*
- 		 * At this point, we should see the main conversion specifier. Whether
- 		 * 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,
--- 4072,4083 ----
  						 errmsg("conversion specifies argument 0, but arguments are numbered from 1")));
  		}
  
  		switch (*cp)
  		{
  			case 's':
  			case 'I':
  			case 'L':
! 				param_feeder(&str, *cp, arg, context);
  				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. */
--- 4091,4153 ----
  	result = cstring_to_text_with_len(str.data, str.len);
  	pfree(str.data);
  
! 	return result;
  }
  
! /*
!  * Assign formated parameter to output StringInfo
!  */
! static void
! text_format_add_converted_arg(StringInfo str, const char conversion, int arg, void *context)
  {
+ 	text_format_context *fctxt = (text_format_context *) context;
+ 	Datum		value;
+ 	bool		isNull;
+ 	Oid		typid;
  	Oid			typOutput;
  	bool		typIsVarlena;
+ 
+ 	/* Not enough arguments?  Deduct 1 to avoid counting format string. */
+ 	if (arg > fctxt->nargs - 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("too few arguments for format")));
+ 
+ 	value = fctxt->fcinfo->arg[arg];
+ 	isNull = fctxt->fcinfo->argnull[arg];
+ 	typid = get_fn_expr_argtype(fctxt->fcinfo->flinfo, arg);
+ 
+ 	/* Stringify. */
+ 	getTypeOutputInfo(typid, &typOutput, &typIsVarlena);
+ 
+ 	text_format_string_conversion(str, conversion, value, isNull, typOutput);
+ }
+ 
+ /*
+  * Returns a formated string
+  */
+ Datum
+ text_format(PG_FUNCTION_ARGS)
+ {
+ 	text_format_context	fctxt;
+ 
+ 	/* When format string is null, returns null */
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	fctxt.fcinfo = fcinfo;
+ 	fctxt.nargs = fcinfo->nargs;
+ 
+ 	PG_RETURN_TEXT_P(text_format_internal(PG_GETARG_TEXT_PP(0),
+ 						    text_format_add_converted_arg,
+ 									    &fctxt));
+ }
+ 
+ /* Format a %s, %I, or %L conversion. */
+ static void
+ text_format_string_conversion(StringInfo buf, char conversion,
+ 							  Datum value, bool isNull, Oid typOutput)
+ {
  	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. */
--- 4163,4168 ----
***************
*** 4156,4158 **** text_format_nv(PG_FUNCTION_ARGS)
--- 4198,4272 ----
  {
  	return text_format(fcinfo);
  }
+ 
+ static void
+ text_format_array_add_converted_arg(StringInfo str, const char conversion, int arg, void *context)
+ {
+ 	text_format_array_context *fctxt = (text_format_array_context *) context;
+ 	Datum		value;
+ 	bool		isNull;
+ 
+ 	/* Not enough arguments?  Deduct 1 to avoid counting format string. */
+ 	if (arg > fctxt->nelems)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("too few arguments for format")));
+ 
+ 	value = fctxt->elem_values[arg - 1];
+ 	isNull = fctxt->elem_nulls[arg - 1];
+ 
+ 	text_format_string_conversion(str, conversion, value, isNull, fctxt->typOutput);
+ }
+ 
+ /*
+  * Returns formated string - parameters are passed in array
+  */
+ Datum
+ text_format_array(PG_FUNCTION_ARGS)
+ {
+ 	text	   *result;
+ 	text_format_array_context	fctxt;
+ 
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		ArrayType *v;
+ 		Oid		elmtype;
+ 		int16		elmlen;
+ 		bool		elmbyval;
+ 		char		elmalign;
+ 		bool		typbyval;
+ 
+ 		v = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 		elmtype = ARR_ELEMTYPE(v);
+ 		get_typlenbyvalalign(elmtype, &elmlen, &elmbyval, &elmalign);
+ 
+ 		deconstruct_array(v, elmtype,
+ 					  elmlen, elmbyval, elmalign,
+ 					  &fctxt.elem_values, &fctxt.elem_nulls,
+ 					  &fctxt.nelems);
+ 
+ 		getTypeOutputInfo(elmtype, &fctxt.typOutput, &typbyval);
+ 	}
+ 	else
+ 	{
+ 		fctxt.nelems = 0;
+ 		fctxt.elem_values = NULL;
+ 		fctxt.elem_nulls = NULL;
+ 		fctxt.typOutput = InvalidOid;
+ 	}
+ 
+ 	result = text_format_internal(PG_GETARG_TEXT_PP(0),
+ 						    text_format_array_add_converted_arg,
+ 									    &fctxt);
+ 
+ 	if (fctxt.elem_values != NULL)
+ 		pfree(fctxt.elem_values);
+ 	if (fctxt.elem_nulls != NULL)
+ 		pfree(fctxt.elem_nulls);
+ 
+ 	PG_RETURN_TEXT_P(result);
+ }
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 2317,2322 **** DATA(insert OID = 3539 ( format		PGNSP PGUID 12 1 0 2276 0 f f f f f f s 2 0 25
--- 2317,2324 ----
  DESCR("format text message");
  DATA(insert OID = 3540 ( format		PGNSP PGUID 12 1 0 0 0 f f f f f f s 1 0 25 "25" _null_ _null_ _null_ _null_  text_format_nv _null_ _null_ _null_ ));
  DESCR("format text message");
+ DATA(insert OID = 3839 ( format_array	PGNSP PGUID 12 1 0 0 0 f f f f f f s 2 0 25 "25 2277" _null_ _null_ _null_ _null_  text_format_array _null_ _null_ _null_ ));
+ DESCR("format text message with array parameters");
  
  DATA(insert OID = 1810 (  bit_length	   PGNSP PGUID 14 1 0 0 0 f f f f t f i 1 0 23 "17" _null_ _null_ _null_ _null_ "select pg_catalog.octet_length($1) * 8" _null_ _null_ _null_ ));
  DESCR("length in bits");
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 790,795 **** extern Datum text_right(PG_FUNCTION_ARGS);
--- 790,796 ----
  extern Datum text_reverse(PG_FUNCTION_ARGS);
  extern Datum text_format(PG_FUNCTION_ARGS);
  extern Datum text_format_nv(PG_FUNCTION_ARGS);
+ extern Datum text_format_array(PG_FUNCTION_ARGS);
  
  /* version.c */
  extern Datum pgsql_version(PG_FUNCTION_ARGS);
*** a/src/test/regress/expected/text.out
--- b/src/test/regress/expected/text.out
***************
*** 241,243 **** select format('Hello %s %s, %2$s %2$s', 'World', 'Hello again');
--- 241,262 ----
   Hello World Hello again, Hello again Hello again
  (1 row)
  
+ -- should fail - cannot use VARIADIC when you call variadic "any" function
+ select format('Hello %s %1$s %s', variadic array['World', 'Hello again']);
+ ERROR:  function format(unknown, text[]) does not exist
+ LINE 1: select format('Hello %s %1$s %s', variadic array['World', 'H...
+                ^
+ HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+ -- format array with format string
+ select format_array('Hello %s %1$s %s', array['World', 'Hello again']);
+          format_array          
+ -------------------------------
+  Hello World World Hello again
+ (1 row)
+ 
+ select format_array('Hello %s %s, %2$s %2$s', array['World', 'Hello again']);
+                    format_array                   
+ --------------------------------------------------
+  Hello World Hello again, Hello again Hello again
+ (1 row)
+ 
*** a/src/test/regress/sql/text.sql
--- b/src/test/regress/sql/text.sql
***************
*** 76,78 **** select format('%1$1', 1);
--- 76,85 ----
  --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');
+ 
+ -- should fail - cannot use VARIADIC when you call variadic "any" function
+ select format('Hello %s %1$s %s', variadic array['World', 'Hello again']);
+ 
+ -- format array with format string
+ select format_array('Hello %s %1$s %s', array['World', 'Hello again']);
+ select format_array('Hello %s %s, %2$s %2$s', array['World', 'Hello again']);
