*** ./doc/src/sgml/func.sgml.orig	2010-08-24 08:30:43.000000000 +0200
--- ./doc/src/sgml/func.sgml	2010-09-09 08:58:12.515512369 +0200
***************
*** 1272,1277 ****
--- 1272,1280 ----
      <primary>encode</primary>
     </indexterm>
     <indexterm>
+     <primary>format</primary>
+    </indexterm>
+    <indexterm>
      <primary>initcap</primary>
     </indexterm>
     <indexterm>
***************
*** 1482,1487 ****
--- 1485,1507 ----
  
        <row>
         <entry>
+         <literal><function>format</function>(<parameter>formatstr</parameter> <type>text</type> 
+         [, <parameter>str</parameter> <type>"any"</type> [, ...] ])</literal>
+        </entry>
+        <entry><type>text</type></entry>
+        <entry>
+          This functions can be used to create a formated string or message. There are allowed
+          three types of tags: %s as string, %i as SQL identifiers and %l as SQL literals. Attention:
+          result for %i and %l must not be same as result of <function>quote_ident</function> and 
+          <function>quote_literal</function> functions, because this function doesn't try to coerce 
+          parameters to <type>text</type> type and directly use a type's output functions.
+        </entry>
+        <entry><literal>format('Hello %s', 'World')</literal></entry>
+        <entry><literal>Hello World</literal></entry>
+       </row>       
+ 
+       <row>
+        <entry>
          <literal><function>encode</function>(<parameter>data</parameter> <type>bytea</type>,
          <parameter>type</parameter> <type>text</type>)</literal>
         </entry>
*** ./src/backend/utils/adt/varlena.c.orig	2010-08-24 08:30:43.000000000 +0200
--- ./src/backend/utils/adt/varlena.c	2010-09-09 08:44:40.450637505 +0200
***************
*** 21,28 ****
--- 21,30 ----
  #include "libpq/md5.h"
  #include "libpq/pqformat.h"
  #include "miscadmin.h"
+ #include "parser/parse_coerce.h"
  #include "parser/scansup.h"
  #include "regex/regex.h"
+ #include "utils/array.h"
  #include "utils/builtins.h"
  #include "utils/bytea.h"
  #include "utils/lsyscache.h"
***************
*** 3702,3704 ****
--- 3704,3860 ----
  
  	PG_RETURN_TEXT_P(result);
  }
+ 
+ /*
+  * Text format - a variadic function replaces %c symbols with entered text.
+  */
+ Datum
+ text_format(PG_FUNCTION_ARGS)
+ {
+ 	text	   *fmt;
+ 	StringInfoData		str;
+ 	char		*cp;
+ 	int			i = 1;
+ 	size_t		len;
+ 	char		*start_ptr;
+ 	char 			*end_ptr;
+ 	text	*result;
+ 
+ 	/* When format string is null, returns null */
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	fmt = PG_GETARG_TEXT_PP(0);
+ 	len = VARSIZE_ANY_EXHDR(fmt);
+ 	start_ptr = VARDATA_ANY(fmt);
+ 	end_ptr = start_ptr + len - 1;
+ 
+ 	initStringInfo(&str);
+ 	for (cp = start_ptr; cp <= end_ptr; cp++)
+ 	{
+ 		/*
+ 		 * there are allowed escape char - '\'
+ 		 */
+ 		if (cp[0] == '\\')
+ 		{
+ 			/* check next char */
+ 			if (cp < end_ptr)
+ 			{
+ 				switch (cp[1])
+ 				{
+ 					case '\\':
+ 					case '%':
+ 						appendStringInfoChar(&str, cp[1]);
+ 						break;
+ 						
+ 					default:
+ 						ereport(ERROR,
+ 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 							 errmsg("unsupported escape sequence \\%c", cp[1])));
+ 				}
+ 				cp++;
+ 			}
+ 			else
+ 				ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("broken escape sequence")));
+ 		}
+ 		else if (cp[0] == '%')
+ 		{
+ 			char 	tag;
+ 			
+ 			/* initial check */
+ 			if (cp == end_ptr)
+ 				ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("missing formating tag")));
+ 		
+ 			if (i >= PG_NARGS())
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 						 errmsg("too few parameters for format function")));
+ 			
+ 			tag = cp[1];
+ 			cp++;
+ 
+ 			if (!PG_ARGISNULL(i))
+ 		        {
+ 				Oid	valtype;
+ 				Datum	value;
+ 				Oid                     typoutput;
+ 				bool            typIsVarlena;
+ 		        
+ 				/* append n-th value */
+ 				value = PG_GETARG_DATUM(i);
+ 				valtype = get_fn_expr_argtype(fcinfo->flinfo, i);
+ 				getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 				
+ 				if (tag == 's')
+ 				{
+ 					/* show it as unspecified string */
+ 					appendStringInfoString(&str, OidOutputFunctionCall(typoutput, value));
+ 				}
+ 				else if (tag == 'i')
+ 				{
+ 					char *target_value;
+ 				
+ 					/* show it as sql identifier */
+ 					target_value = OidOutputFunctionCall(typoutput, value);
+ 					appendStringInfoString(&str, quote_identifier(target_value));
+ 				}
+ 				else if (tag == 'l')
+ 				{
+ 					text *txt;
+ 					text *quoted_txt;
+ 				
+ 					/* get text value and quotize */
+ 					txt = cstring_to_text(OidOutputFunctionCall(typoutput, value));
+ 					quoted_txt = DatumGetTextP(DirectFunctionCall1(quote_literal,
+ 													    PointerGetDatum(txt)));
+ 					appendStringInfoString(&str, text_to_cstring(quoted_txt));
+ 				}
+ 				else
+ 					ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 						 errmsg("unsupported tag \"%%%c\"", tag)));
+ 			}
+ 			else
+ 			{
+ 				if (tag == 'i')
+ 					ereport(ERROR,
+ 						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ 						 errmsg("NULL is used as SQL identifier")));
+ 				else if (tag == 'l')
+ 					appendStringInfoString(&str, "NULL");
+ 				else if (tag != 's')
+ 					ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 						 errmsg("unsupported tag \"%%%c\"", tag)));
+ 			}
+ 			i++;
+ 		}
+ 		else
+ 			appendStringInfoChar(&str, cp[0]);
+ 	}
+ 
+ 	/* check if all arguments are used */
+ 	if (i != PG_NARGS())
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("too many parameters for format function")));
+ 
+ 	result = cstring_to_text_with_len(str.data, str.len);
+ 	pfree(str.data);
+ 
+         PG_RETURN_TEXT_P(result);
+ }
+ 
+ /*
+  * Non variadic text_format function - only wrapper
+  *   Print and check format string
+  */
+ Datum
+ text_format_nv(PG_FUNCTION_ARGS)
+ {
+ 	return text_format(fcinfo);
+ }
*** ./src/include/catalog/pg_proc.h.orig	2010-08-24 08:30:43.000000000 +0200
--- ./src/include/catalog/pg_proc.h	2010-09-09 08:37:11.321512358 +0200
***************
*** 2732,2737 ****
--- 2732,2741 ----
  DESCR("return the last n characters");
  DATA(insert OID = 3062 ( reverse	PGNSP PGUID 12 1 0 0 f f f t f i 1 0 25 "25" _null_ _null_ _null_ _null_  text_reverse  _null_ _null_ _null_ ));
  DESCR("reverse text");
+ DATA(insert OID = 3063 ( format		PGNSP PGUID 12 1 0 2276 f f f f f s 2 0 25 "25 2276" "{25,2276}" "{i,v}" _null_ _null_  text_format _null_ _null_ _null_ ));
+ DESCR("format text message");
+ DATA(insert OID = 3064 ( format		PGNSP PGUID 12 1 0 0 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 = 1810 (  bit_length	   PGNSP PGUID 14 1 0 0 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");
*** ./src/include/utils/builtins.h.orig	2010-08-24 08:30:44.000000000 +0200
--- ./src/include/utils/builtins.h	2010-09-09 08:38:33.001512464 +0200
***************
*** 738,743 ****
--- 738,745 ----
  extern Datum text_left(PG_FUNCTION_ARGS);
  extern Datum text_right(PG_FUNCTION_ARGS);
  extern Datum text_reverse(PG_FUNCTION_ARGS);
+ extern Datum text_format(PG_FUNCTION_ARGS);
+ extern Datum text_format_nv(PG_FUNCTION_ARGS);
  
  /* version.c */
  extern Datum pgsql_version(PG_FUNCTION_ARGS);
*** ./src/test/regress/expected/text.out.orig	2010-08-24 08:30:44.000000000 +0200
--- ./src/test/regress/expected/text.out	2010-09-09 09:09:59.000000000 +0200
***************
*** 118,120 ****
--- 118,139 ----
    5 | ahoj | ahoj
  (11 rows)
  
+ select format('some text');
+   format   
+ -----------
+  some text
+ (1 row)
+ 
+ select format('-- insert into %i (a,b,c) values(%l,%l,%l)', 'My tab', 'Hello', NULL, 10);
+                           format                           
+ -----------------------------------------------------------
+  -- insert into "My tab" (a,b,c) values('Hello',NULL,'10')
+ (1 row)
+ 
+ -- should fail
+ select format('-- insert into %i (a,b,c) values(%l,%l,%l)', NULL, 'Hello', NULL, 10);
+ ERROR:  NULL is used as SQL identifier
+ select format('-- insert into %i (a,b,c) values(%l,%l,%l)', 'My tab', NULL, 'Hello');
+ ERROR:  too few parameters for format function
+ select format('-- insert into %i (a,b,c) values(%l,%l,%l)', 'My tab', 'Hello', NULL, 10, 10);
+ ERROR:  too many parameters for format function
*** ./src/test/regress/sql/text.sql.orig	2010-08-24 08:30:44.000000000 +0200
--- ./src/test/regress/sql/text.sql	2010-09-09 09:17:50.920668808 +0200
***************
*** 41,43 ****
--- 41,49 ----
  select concat_ws(NULL,10,20,null,30) is null;
  select reverse('abcde');
  select i, left('ahoj', i), right('ahoj', i) from generate_series(-5, 5) t(i) order by i;
+ select format('some text');
+ select format('-- insert into %i (a,b,c) values(%l,%l,%l)', 'My tab', 'Hello', NULL, 10);
+ -- should fail
+ select format('-- insert into %i (a,b,c) values(%l,%l,%l)', NULL, 'Hello', NULL, 10);
+ select format('-- insert into %i (a,b,c) values(%l,%l,%l)', 'My tab', NULL, 'Hello');
+ select format('-- insert into %i (a,b,c) values(%l,%l,%l)', 'My tab', 'Hello', NULL, 10, 10);
