*** ./contrib/Makefile.orig	2009-11-18 22:57:56.000000000 +0100
--- ./contrib/Makefile	2010-07-12 10:49:24.393209559 +0200
***************
*** 37,42 ****
--- 37,43 ----
  		pgstattuple	\
  		seg		\
  		spi		\
+ 		stringfunc	\
  		tablefunc	\
  		test_parser	\
  		tsearch2	\
*** ./contrib/stringfunc/expected/stringfunc.out.orig	2010-03-09 13:28:17.082096569 +0100
--- ./contrib/stringfunc/expected/stringfunc.out	2010-07-12 22:06:56.000000000 +0200
***************
*** 0 ****
--- 1,83 ----
+ SET client_min_messages = warning;
+ \set ECHO none
+ RESET client_min_messages;
+ -- sprintf test
+ select sprintf('>>>%10s %10d<<<', 'hello', 10);
+            sprintf           
+ -----------------------------
+  >>>     hello         10<<<
+ (1 row)
+ 
+ select sprintf('>>>%-10s<<<', 'hello');
+      sprintf      
+ ------------------
+  >>>hello     <<<
+ (1 row)
+ 
+ select sprintf('>>>%5.2<<<', 'abcde');
+ ERROR:  unsupported sprintf format tag '<'
+ select sprintf('>>>%*s<<<', 10, 'abcdef');
+      sprintf      
+ ------------------
+  >>>    abcdef<<<
+ (1 row)
+ 
+ select sprintf('>>>%*s<<<', 10); -- error
+ ERROR:  too few parameters specified for printf function
+ select sprintf('%010d', 10);
+   sprintf   
+ ------------
+  0000000010
+ (1 row)
+ 
+ select sprintf('%d', 100.0/3.0);
+  sprintf 
+ ---------
+  33
+ (1 row)
+ 
+ select sprintf('%e', 100.0/3.0);
+    sprintf    
+ --------------
+  3.333333e+01
+ (1 row)
+ 
+ select sprintf('%f', 100.0/3.0);
+   sprintf  
+ -----------
+  33.333333
+ (1 row)
+ 
+ select sprintf('%g', 100.0/3.0);
+  sprintf 
+ ---------
+  33.3333
+ (1 row)
+ 
+ select sprintf('%7.4e', 100.0/3.0);
+   sprintf   
+ ------------
+  3.3333e+01
+ (1 row)
+ 
+ select sprintf('%7.4f', 100.0/3.0);
+  sprintf 
+ ---------
+  33.3333
+ (1 row)
+ 
+ select sprintf('%7.4g', 100.0/3.0);
+  sprintf 
+ ---------
+    33.33
+ (1 row)
+ 
+ /*
+  * concat tests
+  */
+ select concat_ws('#',1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+          concat_ws          
+ ----------------------------
+  1#2#3#hello#t#f#03-09-2010
+ (1 row)
+ 
*** ./contrib/stringfunc/Makefile.orig	2010-03-05 15:05:20.171741922 +0100
--- ./contrib/stringfunc/Makefile	2010-03-05 14:34:31.391738508 +0100
***************
*** 0 ****
--- 1,17 ----
+ # $PostgreSQL: pgsql/contrib/stringfunc/Makefile,v 1.1 2008/07/29 18:31:20 tgl Exp $
+ 
+ MODULES = stringfunc
+ DATA_built = stringfunc.sql
+ DATA = uninstall_stringfunc.sql
+ REGRESS = stringfunc
+ 
+ ifdef USE_PGXS
+ PG_CONFIG = pg_config
+ PGXS := $(shell $(PG_CONFIG) --pgxs)
+ include $(PGXS)
+ else
+ subdir = contrib/stringfunc
+ top_builddir = ../..
+ include $(top_builddir)/src/Makefile.global
+ include $(top_srcdir)/contrib/contrib-global.mk
+ endif
*** ./contrib/stringfunc/sql/stringfunc.sql.orig	2010-03-09 13:14:14.064096132 +0100
--- ./contrib/stringfunc/sql/stringfunc.sql	2010-07-12 22:06:21.589332311 +0200
***************
*** 0 ****
--- 1,28 ----
+ 
+ SET client_min_messages = warning;
+ \set ECHO none
+ \i stringfunc.sql
+ \set ECHO all
+ RESET client_min_messages;
+ 
+ -- sprintf test
+ select sprintf('>>>%10s %10d<<<', 'hello', 10);
+ select sprintf('>>>%-10s<<<', 'hello');
+ select sprintf('>>>%5.2<<<', 'abcde');
+ select sprintf('>>>%*s<<<', 10, 'abcdef');
+ select sprintf('>>>%*s<<<', 10); -- error
+ select sprintf('%010d', 10);
+ 
+ select sprintf('%d', 100.0/3.0);
+ select sprintf('%e', 100.0/3.0);
+ select sprintf('%f', 100.0/3.0);
+ select sprintf('%g', 100.0/3.0);
+ select sprintf('%7.4e', 100.0/3.0);
+ select sprintf('%7.4f', 100.0/3.0);
+ select sprintf('%7.4g', 100.0/3.0);
+ 
+ /*
+  * concat tests
+  */
+ select concat_ws('#',1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ 
*** ./contrib/stringfunc/stringfunc.c.orig	2010-03-05 15:05:30.876738040 +0100
--- ./contrib/stringfunc/stringfunc.c	2010-07-12 20:48:26.896332869 +0200
***************
*** 0 ****
--- 1,609 ----
+ #include "postgres.h"
+ #include "stdio.h"
+ #include "string.h"
+ #include "wchar.h"
+ 
+ #include "catalog/pg_type.h"
+ #include "lib/stringinfo.h"
+ #include "mb/pg_wchar.h"
+ #include "parser/parse_coerce.h"
+ #include "utils/builtins.h"
+ #include "utils/lsyscache.h"
+ #include "utils/memutils.h"
+ #include "utils/pg_locale.h"
+ 
+ PG_MODULE_MAGIC;
+ 
+ #define CHECK_PAD(symbol, pad_value)	\
+ do { \
+ 	if (pdesc->flags & pad_value)		\
+ 		ereport(ERROR,  	\
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE), \
+ 				 errmsg("broken sprintf format"),          \
+ 				 errdetail("Format string is '%s'.", TextDatumGetCString(fmt)), 	   \
+ 				 errhint("Symbol '%c' can be used only one time.", symbol))); \
+ 	pdesc->flags |= pad_value; \
+ } while(0);
+ 
+ /*
+  * string functions
+  */
+ Datum	stringfunc_sprintf(PG_FUNCTION_ARGS);
+ Datum	stringfunc_sprintf_nv(PG_FUNCTION_ARGS);
+ Datum	stringfunc_concat(PG_FUNCTION_ARGS);
+ Datum	stringfunc_concat_ws(PG_FUNCTION_ARGS);
+ Datum	stringfunc_left(PG_FUNCTION_ARGS);
+ Datum	stringfunc_right(PG_FUNCTION_ARGS);
+ Datum	stringfunc_left(PG_FUNCTION_ARGS);
+ Datum	stringfunc_reverse(PG_FUNCTION_ARGS);
+ 
+ /*
+  * V1 registrations
+  */
+ PG_FUNCTION_INFO_V1(stringfunc_sprintf);
+ PG_FUNCTION_INFO_V1(stringfunc_sprintf_nv);
+ PG_FUNCTION_INFO_V1(stringfunc_concat);
+ PG_FUNCTION_INFO_V1(stringfunc_concat_ws);
+ PG_FUNCTION_INFO_V1(stringfunc_reverse);
+ PG_FUNCTION_INFO_V1(stringfunc_left);
+ PG_FUNCTION_INFO_V1(stringfunc_right);
+ 
+ typedef enum {
+     stringfunc_ZERO       =   1,
+     stringfunc_SPACE      =   2,
+     stringfunc_PLUS       =   4,
+     stringfunc_MINUS      =   8,
+     stringfunc_STAR_WIDTH =  16,
+     stringfunc_SHARP      =  32,
+     stringfunc_WIDTH      =  64,
+     stringfunc_PRECISION  = 128,
+     stringfunc_STAR_PRECISION = 256
+ } PlaceholderTags;
+ 
+ typedef struct {
+ 	int	flags;
+ 	char		field_type;
+ 	char		lenmod;
+ 	int32		width;
+ 	int32		precision;
+ } FormatPlaceholderData;
+ 
+ typedef FormatPlaceholderData *PlaceholderDesc;
+ 
+ /*
+  * Static functions
+  */
+ static int mb_string_info(text *str, char **sizes, int **positions);
+ 
+ /*
+  * External
+  */
+ extern PGDLLIMPORT char *days[];
+ 
+ 
+ static Datum 
+ castValueTo(Datum value, Oid targetTypeId, Oid inputTypeId)
+ {
+ 	Oid		funcId;
+ 	CoercionPathType	pathtype;
+ 	FmgrInfo	finfo;
+ 	Datum	   result;
+ 
+ 	if (inputTypeId != UNKNOWNOID)
+ 		pathtype = find_coercion_pathway(targetTypeId, inputTypeId, 
+ 									COERCION_EXPLICIT, 
+ 									&funcId);
+ 	else
+ 		pathtype = COERCION_PATH_COERCEVIAIO;
+ 	
+ 	switch (pathtype)
+ 	{
+ 		case COERCION_PATH_RELABELTYPE:
+ 			result = value;
+ 			break;
+ 		case COERCION_PATH_FUNC:
+ 			{
+ 				Assert(OidIsValid(funcId));
+ 				
+ 				fmgr_info(funcId, &finfo);
+ 				result = FunctionCall1(&finfo, value);
+ 			}
+ 			break;
+ 		
+ 		case COERCION_PATH_COERCEVIAIO:
+ 			{
+ 				Oid                     typoutput;
+ 				Oid			typinput;
+ 				bool            typIsVarlena;
+ 				Oid		typIOParam;
+ 				char 	*extval;
+ 		        
+ 				getTypeOutputInfo(inputTypeId, &typoutput, &typIsVarlena);
+ 				extval = OidOutputFunctionCall(typoutput, value);
+ 				
+ 				getTypeInputInfo(targetTypeId, &typinput, &typIOParam);
+ 				result = OidInputFunctionCall(typinput, extval, typIOParam, -1);
+ 			}
+ 			break;
+ 		
+ 		default:
+ 			elog(ERROR, "failed to find conversion function from %s to %s",
+ 					format_type_be(inputTypeId), format_type_be(targetTypeId));
+ 			/* be compiler quiet */
+ 			result = (Datum) 0;
+ 	}
+ 	
+ 	return result;
+ }
+ 
+ /*
+  * parse and verify sprintf parameter 
+  *
+  *      %[flags][width][.precision]specifier
+  *
+  */
+ static char *
+ parsePlaceholder(char *src, char *end_ptr, PlaceholderDesc pdesc, text *fmt)
+ {
+ 	char		c;
+ 
+ 	pdesc->field_type = '\0';
+ 	pdesc->lenmod = '\0';
+ 	pdesc->flags = 0;
+ 	pdesc->width = 0;
+ 	pdesc->precision = 0;
+ 
+ 	while (src < end_ptr && pdesc->field_type == '\0')
+ 	{
+ 		c = *++src;
+ 
+ 		switch (c)
+ 		{
+ 			case '0':
+ 				CHECK_PAD('0', stringfunc_ZERO);
+ 				break;
+ 			case ' ':
+ 				CHECK_PAD(' ', stringfunc_SPACE);
+ 				break;
+ 			case '+':
+ 				CHECK_PAD('+', stringfunc_PLUS);
+ 				break;
+ 			case '-':
+ 				CHECK_PAD('-', stringfunc_MINUS);
+ 				break;
+ 			case '*':
+ 				CHECK_PAD('*', stringfunc_STAR_WIDTH);
+ 				break;
+ 			case '#':
+ 				CHECK_PAD('#', stringfunc_SHARP);
+ 				break;
+ 			case 'o': case 'i': case 'e': case 'E': case 'f': 
+ 			case 'g': case 'd': case 's': case 'x': case 'X': 
+ 				pdesc->field_type = *src;
+ 				break;
+ 			case '1': case '2': case '3': case '4':
+ 			case '5': case '6': case '7': case '8': case '9':
+ 				CHECK_PAD('9', stringfunc_WIDTH);
+ 				pdesc->width = c - '0';
+ 				while (src < end_ptr && isdigit(src[1]))
+ 					pdesc->width = pdesc->width * 10 + *++src - '0';
+ 				break;
+ 			case '.':
+ 				if (src < end_ptr)
+ 				{
+ 					if (src[1] == '*')
+ 					{
+ 						CHECK_PAD('.', stringfunc_STAR_PRECISION);
+ 						src++;
+ 						elog(NOTICE, "1");
+ 					}
+ 					else
+ 					{
+ 						bool valid = false;
+ 					
+ 						CHECK_PAD('.', stringfunc_PRECISION);
+ 						while (src < end_ptr && isdigit(src[1]))
+ 						{
+ 							pdesc->precision = pdesc->precision * 10 + *++src - '0';
+ 							valid = true;
+ 						}
+ 						
+ 						if (!valid)
+ 							ereport(ERROR,
+ 								(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 								 errmsg("broken sprinf format"),
+ 								 errdetail("missing precision value")));
+ 					}
+ 				}
+ 				else 
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 							 errmsg("broken sprinf format"),
+ 							 errdetail("missing precision value")));
+ 				break;
+ 
+ 			default:
+ 				ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("unsupported sprintf format tag '%c'", c)));
+ 		}
+ 	}
+ 
+ 	if (pdesc->field_type == '\0')
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("broken sprintf format")));
+ 
+ 	return src;
+ }
+ 
+ static char *
+ currentFormat(StringInfo str, PlaceholderDesc pdesc)
+ {
+ 	resetStringInfo(str);
+ 	appendStringInfoChar(str,'%');
+ 	
+ 	if (pdesc->flags & stringfunc_ZERO)
+ 		appendStringInfoChar(str, '0');
+ 
+ 	if (pdesc->flags & stringfunc_MINUS)
+ 		appendStringInfoChar(str, '-');
+ 
+ 	if (pdesc->flags & stringfunc_PLUS)
+ 		appendStringInfoChar(str, '+');
+ 		
+ 	if (pdesc->flags & stringfunc_SPACE)
+ 		appendStringInfoChar(str, ' ');
+ 		
+ 	if (pdesc->flags & stringfunc_SHARP)
+ 		appendStringInfoChar(str, '#');
+ 
+ 	if ((pdesc->flags & stringfunc_WIDTH) || (pdesc->flags & stringfunc_STAR_WIDTH))
+ 		appendStringInfoChar(str, '*');
+ 		
+ 	if ((pdesc->flags & stringfunc_PRECISION) || (pdesc->flags & stringfunc_STAR_PRECISION))
+ 		appendStringInfoString(str, ".*");
+ 		
+ 	if (pdesc->lenmod != '\0')
+ 		appendStringInfoChar(str, pdesc->lenmod);
+ 
+ 	appendStringInfoChar(str, pdesc->field_type);
+ 	
+ 	return str->data;
+ }
+ 
+ /*
+  * simulate %+width.precion%s format of sprintf function 
+  */
+ static void 
+ append_string(StringInfo str,  PlaceholderDesc pdesc, char *string)
+ {
+ 	int	nchars = 0;				/* length of substring in chars */
+ 	int	binlen;				/* length of substring in bytes */
+ 
+ 	/* show only first n chars  */
+ 	if (pdesc->flags & stringfunc_PRECISION)
+ 	{
+ 		char *ptr = string;
+ 		int	  len = pdesc->precision;
+ 		
+ 		if (pg_database_encoding_max_length() > 1)
+ 		{
+ 			while (*ptr && len > 0)
+ 			{
+ 				ptr += pg_mblen(ptr);
+ 				len--;
+ 				nchars++;
+ 			}
+ 		}
+ 		else
+ 		{
+ 			while (*ptr && len > 0)
+ 			{
+ 				ptr++;
+ 				len--;
+ 				nchars++;
+ 			}
+ 		}
+ 		
+ 		binlen = ptr - string;
+ 	}
+ 	else
+ 	{
+ 		/* there isn't precion specified, show complete string */
+ 		nchars = pg_mbstrlen(string);
+ 		binlen = strlen(string);
+ 	}
+ 	
+ 	/* when width is specified, then we have to solve left or right align */
+ 	if (pdesc->flags & stringfunc_WIDTH)
+ 	{
+ 		if (pdesc->width > nchars)
+ 		{
+ 			/* add neccessary spaces to begin or end */
+ 			if (pdesc->flags & stringfunc_MINUS)
+ 			{
+ 				/* allign to left */
+ 				appendBinaryStringInfo(str, string, binlen);
+ 				appendStringInfoSpaces(str, pdesc->width - nchars);
+ 			}
+ 			else
+ 			{
+ 				/* allign to right */
+ 				appendStringInfoSpaces(str, pdesc->width - nchars);
+ 				appendBinaryStringInfo(str, string, binlen);
+ 			}
+ 
+ 		}
+ 		else
+ 			/* just copy result to output */
+ 			appendBinaryStringInfo(str, string, binlen);
+ 	}
+ 	else
+ 		/* just copy result to output */
+ 		appendBinaryStringInfo(str, string, binlen);
+ }
+ 
+ /*
+  * Set width and precision when they are defined dynamicaly
+  */
+ static 
+ int setWidthAndPrecision(PlaceholderDesc pdesc, FunctionCallInfoData *fcinfo, int current)
+ {
+ 
+ 	/* 
+ 	 * don't allow ambiguous definition
+ 	 */
+ 	if ((pdesc->flags & stringfunc_WIDTH) && (pdesc->flags & stringfunc_STAR_WIDTH))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("broken sprintf format"),
+ 				 errdetail("ambiguous width definition")));
+ 
+ 	if ((pdesc->flags & stringfunc_PRECISION) && (pdesc->flags & stringfunc_STAR_PRECISION))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("broken sprintf format"),
+ 				 errdetail("ambiguous precision definition")));
+ 	if (pdesc->flags & stringfunc_STAR_WIDTH)
+ 	{
+ 		if (current >= PG_NARGS())
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("too few parameters specified for printf function")));
+ 		
+ 		if (PG_ARGISNULL(current))
+ 			ereport(ERROR,
+ 				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ 				 errmsg("null value not allowed"),
+ 				 errhint("width (%dth) arguments is NULL", current)));
+ 		
+ 		pdesc->width = DatumGetInt32(castValueTo(PG_GETARG_DATUM(current), INT4OID, 
+ 									get_fn_expr_argtype(fcinfo->flinfo, current)));
+ 		/* reset flag */
+ 		pdesc->flags ^= stringfunc_STAR_WIDTH;
+ 		pdesc->flags |= stringfunc_WIDTH;
+ 		current += 1;
+ 	}
+ 	
+ 	if (pdesc->flags & stringfunc_STAR_PRECISION)
+ 	{
+ 		if (current >= PG_NARGS())
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("too few parameters specified for printf function")));
+ 		
+ 		if (PG_ARGISNULL(current))
+ 			ereport(ERROR,
+ 				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ 				 errmsg("null value not allowed"),
+ 				 errhint("width (%dth) arguments is NULL", current)));
+ 		
+ 		pdesc->precision = DatumGetInt32(castValueTo(PG_GETARG_DATUM(current), INT4OID, 
+ 									get_fn_expr_argtype(fcinfo->flinfo, current)));
+ 		/* reset flags */
+ 		pdesc->flags ^= stringfunc_STAR_PRECISION;
+ 		pdesc->flags |= stringfunc_PRECISION;
+ 		current += 1;
+ 	}
+ 	
+ 	return current;
+ }
+ 
+ /*
+  * sprintf function - it is wrapper for libc vprintf function
+  *
+  *    ensure PostgreSQL -> C casting
+  */
+ Datum
+ stringfunc_sprintf(PG_FUNCTION_ARGS)
+ {
+ 	text	   *fmt;
+ 	StringInfoData   str;
+ 	StringInfoData   format_str;
+ 	char		*cp;
+ 	int			i = 1;
+ 	size_t		len;
+ 	char		*start_ptr,
+ 				*end_ptr;
+ 	FormatPlaceholderData		pdesc;
+ 	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);
+ 	initStringInfo(&format_str);
+ 
+ 	for (cp = start_ptr; cp <= end_ptr; cp++)
+ 	{
+ 		if (cp[0] == '%')
+ 		{
+ 			/* when cp is not pointer on last char, check %% */
+ 			if (cp < end_ptr && cp[1] == '%')
+ 			{
+ 				appendStringInfoChar(&str, cp[1]);
+ 				cp++;
+ 				continue;
+ 			}
+ 
+ 			cp = parsePlaceholder(cp, end_ptr, &pdesc, fmt);
+ 			i = setWidthAndPrecision(&pdesc, fcinfo, i);
+ 			
+ 			if (i >= PG_NARGS())
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 						 errmsg("too few parameters specified for printf function")));
+ 
+ 
+ 			if (!PG_ARGISNULL(i))
+ 		        {
+ 				Oid	valtype;
+ 				Datum	value;
+ 
+ 				/* append n-th value */
+ 				value = PG_GETARG_DATUM(i);
+ 				valtype = get_fn_expr_argtype(fcinfo->flinfo, i);
+ 				
+ 				/* convert value to target type */
+ 				switch (pdesc.field_type)
+ 				{
+ 					case 'o': case 'd': case 'i': case 'x': case 'X':
+ 						{
+ 							int64	target_value;
+ 							const char 		*format;
+ 							
+ 							pdesc.lenmod = 'l';
+ 							target_value = DatumGetInt64(castValueTo(value, INT8OID, valtype));
+ 							format = currentFormat(&format_str, &pdesc);
+ 							
+ 							if ((pdesc.flags & stringfunc_WIDTH) && (pdesc.flags & stringfunc_PRECISION))
+ 								appendStringInfo(&str, format, pdesc.width, pdesc.precision, target_value);
+ 							else if (pdesc.flags & stringfunc_WIDTH)
+ 								appendStringInfo(&str, format, pdesc.width, target_value);
+ 							else if (pdesc.flags & stringfunc_PRECISION)
+ 								appendStringInfo(&str, format, pdesc.precision, target_value);
+ 							else
+ 								appendStringInfo(&str, format, target_value);
+ 						}
+ 						break;
+ 					case 'e': case 'f': case 'g': case 'G': case 'E':
+ 						{
+ 							float8	target_value;
+ 							const char 		*format;
+ 							
+ 							target_value = DatumGetFloat8(castValueTo(value, FLOAT8OID, valtype));
+ 							format = currentFormat(&format_str, &pdesc);
+ 							
+ 							if ((pdesc.flags & stringfunc_WIDTH) && (pdesc.flags & stringfunc_PRECISION))
+ 								appendStringInfo(&str, format, pdesc.width, pdesc.precision, target_value);
+ 							else if (pdesc.flags & stringfunc_WIDTH)
+ 								appendStringInfo(&str, format, pdesc.width, target_value);
+ 							else if (pdesc.flags & stringfunc_PRECISION)
+ 								appendStringInfo(&str, format, pdesc.precision, target_value);
+ 							else
+ 								appendStringInfo(&str, format, target_value);
+ 						}
+ 						break;
+ 					case 's':
+ 						{
+ 							char		*target_value;
+ 							Oid                     typoutput;
+ 							bool            typIsVarlena;
+ 
+ 							getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 							target_value = OidOutputFunctionCall(typoutput, value);
+ 							
+ 							append_string(&str, &pdesc, target_value);
+ 							pfree(target_value);
+ 						}
+ 						break;
+ 				}
+ 			}
+ 			else
+ 				/* return null when some argument is null */
+ 				PG_RETURN_NULL();
+ 			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 printf function")));
+ 	result = cstring_to_text_with_len(str.data, str.len);
+ 	
+ 	pfree(str.data);
+ 	pfree(format_str.data);
+ 
+ 	PG_RETURN_TEXT_P(result);
+ }
+ 
+ /*
+  * only wrapper
+  */
+ Datum
+ stringfunc_sprintf_nv(PG_FUNCTION_ARGS)
+ {
+ 	return stringfunc_sprintf(fcinfo);
+ }
+ 
+ /*
+  * Concat values. First argument is separator. This function
+  * is NULL safe. NULL values are skipped.
+  */
+ Datum
+ stringfunc_concat_ws(PG_FUNCTION_ARGS)
+ {
+ 	StringInfoData   str;
+ 	text *result;
+ 	char	*sepstr;
+ 	int	i;
+ 	
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	/* return NULL, if there are not any parameter */
+ 	if (PG_NARGS() == 1)
+ 		PG_RETURN_NULL();
+ 
+ 	sepstr = text_to_cstring(PG_GETARG_TEXT_P(0));
+ 	initStringInfo(&str);
+ 
+ 	for(i = 1; i < PG_NARGS(); i++)
+ 	{
+ 		if (i > 1)
+ 			appendStringInfoString(&str, sepstr);
+ 
+ 		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);
+ 			appendStringInfoString(&str, OidOutputFunctionCall(typoutput, value));
+ 		}
+ 	}
+ 
+ 	result = cstring_to_text_with_len(str.data, str.len);
+ 	pfree(str.data);
+ 
+ 	PG_RETURN_TEXT_P(result);
+ }
+ 
+ 
*** ./contrib/stringfunc/stringfunc.sql.in.orig	2010-07-08 11:27:31.877299385 +0200
--- ./contrib/stringfunc/stringfunc.sql.in	2010-07-12 22:08:36.012207679 +0200
***************
*** 0 ****
--- 1,14 ----
+ CREATE OR REPLACE FUNCTION sprintf(fmt text, VARIADIC args "any")
+ RETURNS text 
+ AS 'MODULE_PATHNAME','stringfunc_sprintf'
+ LANGUAGE C STABLE;
+ 
+ CREATE OR REPLACE FUNCTION sprintf(fmt text)
+ RETURNS text 
+ AS 'MODULE_PATHNAME','stringfunc_sprintf_nv'
+ LANGUAGE C STABLE;
+ 
+ CREATE OR REPLACE FUNCTION concat_ws(separator text, VARIADIC args "any")
+ RETURNS text 
+ AS 'MODULE_PATHNAME','stringfunc_concat_ws'
+ LANGUAGE C STABLE;
*** ./contrib/stringfunc/uninstall_stringfunc.sql.orig	2010-03-05 15:05:47.260738126 +0100
--- ./contrib/stringfunc/uninstall_stringfunc.sql	2010-07-12 22:05:17.990209025 +0200
***************
*** 0 ****
--- 1,3 ----
+ DROP FUNCTION sprintf(fmt text, VARIADIC args "any");
+ DROP FUNCTION sprintf(fmt text);
+ DROP FUNCTION concat_ws(separator text, VARIADIC args "any");
*** ./doc/src/sgml/contrib.sgml.orig	2010-01-29 00:59:52.000000000 +0100
--- ./doc/src/sgml/contrib.sgml	2010-03-05 15:12:18.861738045 +0100
***************
*** 113,118 ****
--- 113,119 ----
   &seg;
   &contrib-spi;
   &sslinfo;
+  &stringfunc;
   &tablefunc;
   &test-parser;
   &tsearch2;
*** ./doc/src/sgml/filelist.sgml.orig	2010-02-22 12:47:30.000000000 +0100
--- ./doc/src/sgml/filelist.sgml	2010-03-09 15:04:46.210861483 +0100
***************
*** 123,128 ****
--- 123,129 ----
  <!entity pgstattuple     SYSTEM "pgstattuple.sgml">
  <!entity pgtrgm          SYSTEM "pgtrgm.sgml">
  <!entity seg             SYSTEM "seg.sgml">
+ <!entity stringfunc      SYSTEM "stringfunc.sgml">
  <!entity contrib-spi     SYSTEM "contrib-spi.sgml">
  <!entity sslinfo         SYSTEM "sslinfo.sgml">
  <!entity tablefunc       SYSTEM "tablefunc.sgml">
*** ./doc/src/sgml/func.sgml.orig	2010-03-03 23:28:42.000000000 +0100
--- ./doc/src/sgml/func.sgml	2010-07-12 22:25:58.889336385 +0200
***************
*** 1262,1267 ****
--- 1262,1270 ----
      <primary>encode</primary>
     </indexterm>
     <indexterm>
+     <primary>format</primary>
+    </indexterm>
+    <indexterm>
      <primary>initcap</primary>
     </indexterm>
     <indexterm>
***************
*** 1372,1377 ****
--- 1375,1393 ----
  
        <row>
         <entry>
+         <literal><function>concat</function>(<parameter>str</parameter> <type>"any"</type>,
+          [, <parameter>str</parameter> <type>"any"</type> [...] ])</literal>
+        </entry>
+        <entry><type>text</type></entry>
+        <entry>
+         Returns concated strings. NULLs are ignored.
+        </entry>
+        <entry><literal>concat('abcde', 2, NULL, 22)</literal></entry>
+        <entry><literal>abcde222</literal></entry>
+       </row>       
+ 
+       <row>
+        <entry>
          <literal><function>convert</function>(<parameter>string</parameter> <type>bytea</type>,
          <parameter>src_encoding</parameter> <type>name</type>,
          <parameter>dest_encoding</parameter> <type>name</type>)</literal>
***************
*** 1450,1455 ****
--- 1466,1486 ----
        </row>       
  
        <row>
+        <entry>
+         <literal><function>format</function>(<parameter>formatstr</parameter> <type>text</type>
+         [, <parameter>value</parameter> <type>any</type> [...] ])</literal>
+        </entry>
+        <entry><type>text</type></entry>
+        <entry>
+         The format string can be followed by optional argument values to be inserted into 
+         the result string. Inside the format string, % is replaced by the string representation 
+         of the next optional argument's value. Write %% to emit a literal %.
+        </entry>
+        <entry><literal>format('% % xxx: %', 10, 'foo', current_date)</literal></entry>
+        <entry><literal>10 foo xxx: 2010-03-05</literal></entry>
+       </row>       
+ 
+       <row>
         <entry><literal><function>initcap</function>(<parameter>string</parameter>)</literal></entry>
         <entry><type>text</type></entry>
         <entry>
***************
*** 1462,1467 ****
--- 1493,1512 ----
        </row>
  
        <row>
+        <entry>
+         <literal><function>left</function>(<parameter>str</parameter> <type>text</type>,
+         <parameter>n</parameter> <type>int</type> )</literal>
+        </entry>
+        <entry><type>text</type></entry>
+        <entry>
+         Returns first n chars of string. When n is negative, then returns chars from begin
+         to n char from right.
+        </entry>
+        <entry><literal>left('abcde', 2)</literal></entry>
+        <entry><literal>ab</literal></entry>
+       </row>       
+ 
+       <row>
         <entry><literal><function>length</function>(<parameter>string</parameter>)</literal></entry>
         <entry><type>int</type></entry>
         <entry>
***************
*** 1676,1681 ****
--- 1721,1758 ----
  
        <row>
         <entry>
+         <literal><function>reverse</function>(<parameter>str</parameter>)</literal>
+        </entry>
+        <entry><type>text</type></entry>
+        <entry>
+         Returns reversed string.
+        </entry>
+        <entry><literal>reverse('abcde')</literal></entry>
+        <entry><literal>edcba</literal></entry>
+       </row>       
+ 
+       <row>
+        <entry>
+         <literal><function>right</function>(<parameter>str</parameter> <type>text</type>,
+          <parameter>n</parameter> <type>int</type> )</literal>
+        </entry>
+        <entry><type>text</type></entry>
+        <entry>
+         Returns last n chars of string. When n is negative, then returns chars from n char
+         to end of string
+        </entry>
+        <entry><literal>right('abcde', 2)</literal></entry>
+        <entry><literal>de</literal></entry>
+       </row>       
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+       <row>
+        <entry>
          <literal><function>rpad</function>(<parameter>string</parameter> <type>text</type>,
          <parameter>length</parameter> <type>int</type>
          <optional>, <parameter>fill</parameter> <type>text</type></optional>)</literal>
*** ./doc/src/sgml/stringfunc.sgml.orig	2010-03-05 15:11:23.284738364 +0100
--- ./doc/src/sgml/stringfunc.sgml	2010-07-12 22:13:39.950213852 +0200
***************
*** 0 ****
--- 1,45 ----
+ <!-- $PostgreSQL: pgsql/doc/src/sgml/stringfunc.sgml,v 1.2 2008/09/12 18:29:49 tgl Exp $ -->
+ 
+ <sect1 id="stringfunc">
+  <title>stringfunc</title>
+ 
+  <indexterm zone="stringfunc">
+   <primary>stringfunc</primary>
+  </indexterm>
+ 
+  <para>
+   The <filename>stringfunc</> module provides a additional function
+   for operation over strings. These functions can be used as patter
+   for developing a variadic functions.
+  </para>
+ 
+  <sect2>
+   <title>How to Use It</title>
+ 
+   <para>
+    Here's a simple example of usage:
+ 
+   <programlisting>
+    SELECT sprintf('formated number: %10d',10);
+   </programlisting>
+   </para>
+ 
+   <para>
+    Nodul contains following functions:
+   </para>
+ 
+   <itemizedlist>
+    <listitem>
+     <para>
+       <function>sprintf(formatstr [, params])</> clasic sprintf function
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>concat_ws(separator, param1 [, param2 [,...]])()</> a concation two 
+       or more strings. First parameter is used as separator.
+     </para>
+    </listitem>
+   </itemizedlist>
+  </sect2>
+ </sect1>
*** ./src/backend/utils/adt/varlena.c.orig	2010-02-26 03:01:10.000000000 +0100
--- ./src/backend/utils/adt/varlena.c	2010-07-12 21:09:24.472333397 +0200
***************
*** 74,79 ****
--- 74,80 ----
  				bool length_not_specified);
  static bytea *bytea_overlay(bytea *t1, bytea *t2, int sp, int sl);
  static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
+ static int mb_string_info(text *str, char **sizes, int **positions, int maxchars);
  
  
  /*****************************************************************************
***************
*** 3415,3417 ****
--- 3416,3757 ----
  	else
  		PG_RETURN_NULL();
  }
+ 
+ /*
+  * Text format - a variadic function replaces % 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,
+ 				*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++)
+ 	{
+ 		if (cp[0] == '%')
+ 		{
+ 
+ 			/* when cp is not pointer on last char, check %% */
+ 			if (cp < end_ptr && cp[1] == '%')
+ 			{
+ 				appendStringInfoChar(&str, cp[1]);
+ 				cp++;
+ 				continue;
+ 			}
+ 
+ 			if (i >= PG_NARGS())
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 						 errmsg("too few parameters for format function")));
+ 
+ 			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);
+ 				appendStringInfoString(&str, OidOutputFunctionCall(typoutput, value));
+ 			}
+ 			else
+ 				/* show same NULL string like RAISE statement */
+ 				appendStringInfoString(&str, "<NULL>");
+ 			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);
+ }
+ 
+ /*
+  * Concat values to comma separated list. This function
+  * is NULL safe. NULL values are skipped.
+  */
+ Datum
+ text_concat(PG_FUNCTION_ARGS)
+ {
+ 	StringInfoData   str;
+ 	int	i;
+ 	text	*result;
+ 
+ 	/* return NULL, if there are not any parameter */
+ 	if (PG_NARGS() == 0)
+ 		PG_RETURN_NULL();
+ 
+ 	initStringInfo(&str);
+ 	
+ 	for(i = 0; i < PG_NARGS(); i++)
+ 	{
+ 		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);
+ 			appendStringInfoString(&str, OidOutputFunctionCall(typoutput, value));
+ 		}
+ 	}
+ 	
+ 	result = cstring_to_text_with_len(str.data, str.len);
+ 	pfree(str.data);
+ 
+ 	PG_RETURN_TEXT_P(result);
+ }
+ 
+ 
+ /*
+  * Returns length of string, size and position of every
+  * multibyte char in string. When this function is used on 
+  * left substring, then we can get info only about first 
+  * maxchars chars.
+  */
+ static int
+ mb_string_info(text *str, char **sizes, int **positions, 
+ 							    int maxchars)
+ {
+ 	int r_len;
+ 	int cur_size = 0;
+ 	int sz;
+ 	char *p;
+ 	int cur = 0;
+ 
+ 	p = VARDATA_ANY(str);
+ 	r_len = VARSIZE_ANY_EXHDR(str);
+ 
+ 	if (NULL != sizes)
+ 		*sizes = palloc(r_len * sizeof(char));
+ 	if (NULL != positions)
+ 		*positions = palloc(r_len * sizeof(int));
+ 
+ 	while (cur < r_len)
+ 	{
+ 		sz = pg_mblen(p);
+ 		if (sizes)
+ 			(*sizes)[cur_size] = sz;
+ 		if (positions)
+ 			(*positions)[cur_size] = cur;
+ 		cur += sz;
+ 		p += sz;
+ 		cur_size += 1;
+ 		
+ 		/* stop early when we don't need more chars */
+ 		if (maxchars != -1 && --maxchars == 0)
+ 			break;
+ 	}
+ 
+ 	return cur_size;
+ }
+ 
+ 
+ /*
+  * Returns first n chars. When n is negative, then
+  * it returns chars from n+1 position.
+  */
+ Datum
+ text_left(PG_FUNCTION_ARGS)
+ {
+ 	text *str = PG_GETARG_TEXT_PP(0);
+ 	int	len = VARSIZE_ANY_EXHDR(str);
+ 	char	*p = VARDATA_ANY(str);
+ 	text   *result;
+ 	int		n = PG_GETARG_INT32(1);
+ 	
+ 	if (len == 0 || n == 0)
+ 		PG_RETURN_TEXT_P(cstring_to_text(""));
+ 	
+ 	if (pg_database_encoding_max_length() > 1)
+ 	{
+ 		char	*sizes;
+ 		int	*positions;
+ 		
+ 		if (n > 0)
+ 		{
+ 			len = mb_string_info(str, &sizes, &positions, n);
+ 			n = n > len ? len : n;
+ 			result = cstring_to_text_with_len(p, positions[n - 1] + sizes[n - 1]);
+ 		}
+ 		else
+ 		{
+ 			len = mb_string_info(str, &sizes, &positions, -1);
+ 			n = -n > len ? len : -n;
+ 			result = cstring_to_text_with_len(p, positions[len - n - 1] + sizes[len - n - 1]);
+ 		}
+ 		
+ 		pfree(positions);
+ 		pfree(sizes);
+ 	}
+ 	else
+ 	{
+ 		if (n > 0)
+ 		{
+ 			n = n > len ? len : n;
+ 			result = cstring_to_text_with_len(p, n);
+ 		}
+ 		else
+ 		{
+ 			n = -n > len ? len : -n;
+ 			result = cstring_to_text_with_len(p, len - n);
+ 		}
+ 	}
+ 
+ 	PG_RETURN_TEXT_P(result);
+ }
+ 
+ /*
+  * Returns last n chars from string. When n is negative,
+  * then returns string without last n chars.
+  */
+ Datum
+ text_right(PG_FUNCTION_ARGS)
+ {
+ 	text *str = PG_GETARG_TEXT_PP(0);
+ 	int	len = VARSIZE_ANY_EXHDR(str);
+ 	char	*p = VARDATA_ANY(str);
+ 	text   *result;
+ 	int		n = PG_GETARG_INT32(1);
+ 	
+ 	if (len == 0 || n == 0)
+ 		PG_RETURN_TEXT_P(CStringGetTextDatum(""));
+ 	
+ 	if (pg_database_encoding_max_length() > 1)
+ 	{
+ 		char	*sizes;
+ 		int	*positions;
+ 		
+ 		len = mb_string_info(str, &sizes, &positions, -1);
+ 		
+ 		if (n > 0)
+ 		{
+ 			n = n > len ? len : n;
+ 			result = cstring_to_text_with_len(p + positions[len - n],
+ 								positions[len - 1] + sizes[len - 1] - positions[len - n]);
+ 		}
+ 		else
+ 		{
+ 			if (-n < len)
+ 			{
+ 				n = -n > len ? len : -n;
+ 				result = cstring_to_text_with_len(p + positions[n],
+ 									positions[len - 1] + sizes[len - 1] - positions[n]);
+ 			}
+ 			else
+ 			{
+ 				/* return empty string */
+ 				result = cstring_to_text_with_len("", 0);
+ 			}
+ 		}
+ 		
+ 		pfree(positions);
+ 		pfree(sizes);
+ 	}
+ 	else
+ 	{
+ 		if (n > 0)
+ 		{
+ 			n = n > len ? len : n;
+ 			result = cstring_to_text_with_len(p + len - n, n);
+ 		}
+ 		else
+ 		{
+ 			n = -n > len ? len : -n;
+ 			result = cstring_to_text_with_len(p + n, len - n);
+ 		}
+ 	}
+ 
+ 	PG_RETURN_TEXT_P(result);
+ }
+ 
+ /*
+  * Returns reversed string
+  */
+ Datum
+ text_reverse(PG_FUNCTION_ARGS)
+ {
+ 	text *str;
+ 	text	*result;
+ 	char	*p;
+ 	int  len;
+ 	char	*data;
+ 	int		i;
+ 
+ 	str = PG_GETARG_TEXT_PP(0);
+ 	p = VARDATA_ANY(str);
+ 	len = VARSIZE_ANY_EXHDR(str);
+ 
+ 	result = palloc(len + VARHDRSZ);
+ 	data = (char*) VARDATA(result);
+ 	SET_VARSIZE(result, len + VARHDRSZ);
+ 	
+ 	if (pg_database_encoding_max_length() > 1)
+ 	{
+ 		char	*sizes;
+ 		int	*positions;
+ 		
+ 		/* multibyte version */
+ 		len = mb_string_info(str, &sizes, &positions, -1);
+ 		for (i = len - 1; i >= 0; i--)
+ 		{
+ 			memcpy(data, p + positions[i], sizes[i]);
+ 			data += sizes[i];
+ 		}
+ 		
+ 		pfree(positions);
+ 		pfree(sizes);
+ 	}
+ 	else
+ 	{
+ 		/* single byte version */
+ 		for (i = len - 1; i >= 0; i--)
+ 			*data++ = p[i];
+ 	}
+ 	
+ 	PG_RETURN_TEXT_P(result);
+ }
*** ./src/include/catalog/pg_proc.h.orig	2010-02-26 03:01:21.000000000 +0100
--- ./src/include/catalog/pg_proc.h	2010-07-12 22:01:17.436332903 +0200
***************
*** 2712,2717 ****
--- 2712,2729 ----
  DATA(insert OID = 1799 (  oidout		   PGNSP PGUID 12 1 0 0 f f f t f i 1 0 2275 "26" _null_ _null_ _null_ _null_ oidout _null_ _null_ _null_ ));
  DESCR("I/O");
  
+ DATA(insert OID = 3094 ( concat			PGNSP PGUID 12 1 0 2276 f f f f f s 1 0 25 "2276" "{2276}" "{v}" _null_ _null_  text_concat _null_ _null_ _null_ ));
+ DESCR("concat values to text");
+ DATA(insert OID = 3095 ( left			PGNSP PGUID 12 1 0 0 f f f t f i 2 0 25 "25 23" _null_ _null_ _null_ _null_  text_left _null_ _null_ _null_ ));
+ DESCR("returns first n chars");
+ DATA(insert OID = 3096 ( right			PGNSP PGUID 12 1 0 0 f f f t f i 2 0 25 "25 23" _null_ _null_ _null_ _null_  text_right _null_ _null_ _null_ ));
+ DESCR("returns last n chars");
+ DATA(insert OID = 3097 ( 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("returns reversed string");
+ DATA(insert OID = 3098 ( 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 = 3099 ( 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-02-26 03:01:28.000000000 +0100
--- ./src/include/utils/builtins.h	2010-07-12 21:07:21.301726819 +0200
***************
*** 730,735 ****
--- 730,742 ----
  extern Datum string_agg_delim_transfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_finalfn(PG_FUNCTION_ARGS);
  
+ extern Datum text_format(PG_FUNCTION_ARGS);
+ extern Datum text_format_nv(PG_FUNCTION_ARGS);
+ extern Datum text_concat(PG_FUNCTION_ARGS);
+ extern Datum text_left(PG_FUNCTION_ARGS);
+ extern Datum text_right(PG_FUNCTION_ARGS);
+ extern Datum text_reverse(PG_FUNCTION_ARGS);
+ 
  /* version.c */
  extern Datum pgsql_version(PG_FUNCTION_ARGS);
  
*** ./src/test/regress/expected/text.out.orig	2007-06-07 01:00:50.000000000 +0200
--- ./src/test/regress/expected/text.out	2010-07-12 22:10:17.000000000 +0200
***************
*** 51,53 ****
--- 51,154 ----
  LINE 1: select 3 || 4.0;
                   ^
  HINT:  No operator matches the given name and argument type(s). You might need to add explicit type casts.
+ select format('Hello % %', 'World', 10);
+      format     
+ ----------------
+  Hello World 10
+ (1 row)
+ 
+ select format('users: %, date: %', 10, to_date('20080809','YYYYMMDD'));
+            format            
+ -----------------------------
+  users: 10, date: 08-09-2008
+ (1 row)
+ 
+ select format('Hello');
+  format 
+ --------
+  Hello
+ (1 row)
+ 
+ -- should to fail!
+ select format('Hello %');
+ ERROR:  too few parameters for format function
+ select format('Hello',10);
+ ERROR:  too many parameters for format function
+ /*
+  * concat tests
+  */
+ select concat(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+         concat        
+ ----------------------
+  123hellotf03-09-2010
+ (1 row)
+ 
+ /*
+  * others tests
+  */
+ select reverse('abcde');
+  reverse 
+ ---------
+  edcba
+ (1 row)
+ 
+ select left('ahoj', 2);
+  left 
+ ------
+  ah
+ (1 row)
+ 
+ select left('ahoj', 5);
+  left 
+ ------
+  ahoj
+ (1 row)
+ 
+ select left('ahoj', 0);
+  left 
+ ------
+  
+ (1 row)
+ 
+ select left('ahoj', -1);
+  left 
+ ------
+  aho
+ (1 row)
+ 
+ select left('ahoj', -10);
+  left 
+ ------
+  
+ (1 row)
+ 
+ select right('ahoj', 2);
+  right 
+ -------
+  oj
+ (1 row)
+ 
+ select right('ahoj', 5);
+  right 
+ -------
+  ahoj
+ (1 row)
+ 
+ select right('ahoj', 0);
+  right 
+ -------
+  
+ (1 row)
+ 
+ select right('ahoj', -1);
+  right 
+ -------
+  hoj
+ (1 row)
+ 
+ select right('ahoj', -10);
+  right 
+ -------
+  
+ (1 row)
+ 
*** ./src/test/regress/sql/text.sql.orig	2007-06-07 01:00:50.000000000 +0200
--- ./src/test/regress/sql/text.sql	2010-07-12 22:09:57.149208031 +0200
***************
*** 28,30 ****
--- 28,60 ----
  -- but not this:
  
  select 3 || 4.0;
+ 
+ select format('Hello % %', 'World', 10);
+ select format('users: %, date: %', 10, to_date('20080809','YYYYMMDD'));
+ select format('Hello');
+ 
+ -- should to fail!
+ select format('Hello %');
+ select format('Hello',10);
+ 
+ /*
+  * concat tests
+  */
+ select concat(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ 
+ /*
+  * others tests
+  */
+ select reverse('abcde');
+ 
+ select left('ahoj', 2);
+ select left('ahoj', 5);
+ select left('ahoj', 0);
+ select left('ahoj', -1);
+ select left('ahoj', -10);
+ 
+ select right('ahoj', 2);
+ select right('ahoj', 5);
+ select right('ahoj', 0);
+ select right('ahoj', -1);
+ select right('ahoj', -10);
