patch (for 9.1) string functions

Started by Pavel Stehulealmost 16 years ago66 messages
#1Pavel Stehule
pavel.stehule@gmail.com
1 attachment(s)

Hello

this patch contains a string formatting function "format"

postgres=# select format('some message: % (current user: %)',
current_date, current_user);
format
------------------------------------------------
some message: 2010-03-09 (current user: pavel)
(1 row)

this patch add new contrib module string functions - contains mainly
sprintf function:

postgres=# select sprintf('some message: %10s (%10s)', current_date,
current_user);
sprintf
---------------------------------------
some message: 2010-03-09 ( pavel)
(1 row)

postgres=# select sprintf('some message: %10s (%-10s)', current_date,
current_user);
sprintf
---------------------------------------
some message: 2010-03-09 (pavel )
(1 row)

some string variadic functions

postgres=# select concat('ahaha',10,null,current_date, true);
concat
------------------------
ahaha,10,,2010-03-09,t
(1 row)

postgres=# select concat_sql('ahaha',10,null,current_date, true);
concat_sql
--------------------------------
'ahaha',10,NULL,'2010-03-09',t
(1 row)

postgres=# select concat_json('ahaha'::text,10,null,current_date, true);
concat_json
-----------------------------------
"ahaha",10,null,"2010-03-09",true
(1 row)

and some basic text function rvrs, left, right.

Regards
Pavel Stehule

Attachments:

stringfunc.diffapplication/octet-stream; name=stringfunc.diffDownload
*** ./contrib/stringfunc/expected/stringfunc.out.orig	2010-03-09 13:28:17.082096569 +0100
--- ./contrib/stringfunc/expected/stringfunc.out	2010-03-09 14:22:25.000000000 +0100
***************
*** 0 ****
--- 1,160 ----
+ 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('%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(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+            concat           
+ ----------------------------
+  1,2,3,hello,t,f,03-09-2010
+ (1 row)
+ 
+ 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)
+ 
+ select concat_sql(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+            concat_sql           
+ --------------------------------
+  1,2,3,'hello',t,f,'03-09-2010'
+ (1 row)
+ 
+ select concat_json(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+               concat_json              
+ ---------------------------------------
+  1,2,3,"hello",true,false,"03-09-2010"
+ (1 row)
+ 
+ /*
+  * others tests
+  */
+ select rvrs('abcde');
+  rvrs  
+ -------
+  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)
+ 
*** ./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-03-09 14:02:05.999094713 +0100
***************
*** 0 ****
--- 1,44 ----
+ 
+ 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('%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(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ select concat_ws('#',1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ select concat_sql(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ select concat_json(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ 
+ /*
+  * others tests
+  */
+ select rvrs('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);
*** ./contrib/stringfunc/stringfunc.c.orig	2010-03-05 15:05:30.876738040 +0100
--- ./contrib/stringfunc/stringfunc.c	2010-03-09 14:22:16.995094725 +0100
***************
*** 0 ****
--- 1,1027 ----
+ #include "postgres.h"
+ #include "stdio.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_format(PG_FUNCTION_ARGS);
+ Datum	stringfunc_format_nv(PG_FUNCTION_ARGS);
+ Datum	stringfunc_concat(PG_FUNCTION_ARGS);
+ Datum	stringfunc_concat_ws(PG_FUNCTION_ARGS);
+ Datum	stringfunc_concat_json(PG_FUNCTION_ARGS);
+ Datum	stringfunc_concat_sql(PG_FUNCTION_ARGS);
+ Datum	stringfunc_left(PG_FUNCTION_ARGS);
+ Datum	stringfunc_right(PG_FUNCTION_ARGS);
+ Datum	stringfunc_left(PG_FUNCTION_ARGS);
+ Datum	stringfunc_rvrs(PG_FUNCTION_ARGS);
+ 
+ /*
+  * V1 registrations
+  */
+ PG_FUNCTION_INFO_V1(stringfunc_sprintf);
+ PG_FUNCTION_INFO_V1(stringfunc_sprintf_nv);
+ PG_FUNCTION_INFO_V1(stringfunc_format);
+ PG_FUNCTION_INFO_V1(stringfunc_format_nv);
+ PG_FUNCTION_INFO_V1(stringfunc_concat);
+ PG_FUNCTION_INFO_V1(stringfunc_concat_ws);
+ PG_FUNCTION_INFO_V1(stringfunc_concat_json);
+ PG_FUNCTION_INFO_V1(stringfunc_concat_sql);
+ PG_FUNCTION_INFO_V1(stringfunc_rvrs);
+ 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 char *json_string(char *str);
+ 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;
+ }
+ 
+ /*
+  * 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")));
+ 		
+ 		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")));
+ 		
+ 		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;
+ 			}
+ 
+ 			if (i >= PG_NARGS())
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 						 errmsg("too few parameters")));
+ 						 
+ 			cp = parsePlaceholder(cp, end_ptr, &pdesc, fmt);
+ 			i = setWidthAndPrecision(&pdesc, fcinfo, i);
+ 
+ 			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;
+ 							const char 		*format;
+ 							Oid                     typoutput;
+ 							bool            typIsVarlena;
+ 
+ 							getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 							target_value = OidOutputFunctionCall(typoutput, value);
+ 							
+ 							format = currentFormat(&format_str, &pdesc);
+ 
+ 							/* use wide chars if it is necessary */
+ 							if (pg_database_encoding_max_length() > 1)
+ 							{
+ 								wchar_t *wformat;
+ 								wchar_t	*wbuffer;
+ 								size_t 	fmtlen = (strlen(format) + 1) * sizeof(wchar_t);
+ 								size_t	len = strlen(target_value) + 1;
+ 								
+ 								wformat = palloc(fmtlen);
+ 								char2wchar(wformat, fmtlen, format, strlen(format));
+ 								wbuffer = palloc(len * sizeof(wchar_t));
+ 								
+ 								for (;;)
+ 								{
+ 									int	result;
+ 									
+ 									if ((pdesc.flags & stringfunc_WIDTH) && (pdesc.flags & stringfunc_PRECISION))
+ 										result = swprintf(wbuffer, len, wformat, pdesc.width, 
+ 															 pdesc.precision, target_value);
+ 									else if (pdesc.flags & stringfunc_WIDTH)
+ 										result = swprintf(wbuffer, len, wformat, pdesc.width, target_value);
+ 									else if (pdesc.flags & stringfunc_PRECISION)
+ 										result = swprintf(wbuffer, len, wformat, pdesc.precision, target_value);
+ 									else
+ 										result = swprintf(wbuffer, len, wformat, target_value);
+ 									
+ 									if (result != -1)
+ 									{
+ 										/* append result */
+ 										appendStringInfo(&str, "%ls", wbuffer);
+ 										break;
+ 									}
+ 									else
+ 									{
+ 										/* increase buffer size and repeat */
+ 										len *= 2;
+ 										if ((len * sizeof(pg_wchar)) > MaxAllocSize)
+ 											ereport(ERROR,
+ 												(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ 												 errmsg("out of memory")));
+ 												 
+ 										wbuffer = repalloc(wbuffer, len * sizeof(wchar_t) + 1);
+ 										/* continue */
+ 									}
+ 								}
+ 								
+ 								pfree(wbuffer);
+ 								pfree(wformat);
+ 							}
+ 							else
+ 							{
+ 								/* shortcut for one byte encoding */
+ 								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);
+ 								
+ 								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")));
+ 	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 to comma separated list. This function
+  * is NULL safe. NULL values are skipped.
+  */
+ Datum
+ stringfunc_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 (i > 0)
+ 			appendStringInfoChar(&str, ',');
+ 
+ 		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);
+ }
+ 
+ /*
+  * 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);
+ }
+ 
+ /*
+  * Concat string with respect to SQL format. This is NULL safe.
+  * NULLs values are transformated to "NULL" string.
+  */
+ Datum
+ stringfunc_concat_sql(PG_FUNCTION_ARGS)
+ {
+ 	StringInfoData		str;
+ 	text *result;
+ 	int	i;
+ 
+ 	/* 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 (i > 0)
+ 			appendStringInfoChar(&str, ',');
+ 
+ 		if (!PG_ARGISNULL(i))
+ 		{
+ 			Oid	valtype;
+ 			Datum	value;
+ 			Oid                     typoutput;
+ 			bool            typIsVarlena;
+ 			TYPCATEGORY	typcat;
+ 
+ 			/* append n-th value */
+ 			value = PG_GETARG_DATUM(i);
+ 			valtype = get_fn_expr_argtype(fcinfo->flinfo, i);
+ 			typcat = TypeCategory(valtype);
+ 
+ 			if (typcat == 'N' || typcat == 'B')
+ 			{
+ 				getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 				appendStringInfoString(&str, OidOutputFunctionCall(typoutput, value));
+ 			}
+ 			else
+ 			{
+ 				text	*txt;
+ 				text	*quoted_txt;
+ 			
+ 				getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 				
+ 				/* 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
+ 			appendStringInfoString(&str, "NULL");
+ 	}
+ 
+ 	result = cstring_to_text_with_len(str.data, str.len);
+ 	pfree(str.data);
+ 
+ 	PG_RETURN_TEXT_P(result);
+ }
+ 
+ 
+ /*
+  * Concat string with respect to JSON format. This is NULL safe.
+  * NULLs values are transformated to "null" string.
+  * JSON uses lowercase characters for boolean constants - see www.json.org
+  */
+ Datum
+ stringfunc_concat_json(PG_FUNCTION_ARGS)
+ {
+ 	StringInfoData	str;
+ 	text	*result;
+ 	int i;
+ 
+ 	/* 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 (i > 0)
+ 			appendStringInfoChar(&str, ',');
+ 
+ 		if (!PG_ARGISNULL(i))
+ 		{
+ 			Oid	valtype;
+ 			Datum	value;
+ 			Oid                     typoutput;
+ 			bool            typIsVarlena;
+ 			TYPCATEGORY	typcat;
+ 
+ 			/* append n-th value */
+ 			value = PG_GETARG_DATUM(i);
+ 			valtype = get_fn_expr_argtype(fcinfo->flinfo, i);
+ 			typcat = TypeCategory(valtype);
+ 
+ 			if (typcat == 'N')
+ 			{
+ 				getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 				appendStringInfoString(&str, OidOutputFunctionCall(typoutput, value));
+ 			} else if (typcat == 'B')
+ 			{
+ 				bool	bvalue = PG_GETARG_BOOL(i);
+ 				
+ 				appendStringInfoString(&str, bvalue ? "true" : "false");
+ 			}
+ 			else
+ 			{
+ 				getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 				appendStringInfo(&str, "\"%s\"", json_string(OidOutputFunctionCall(typoutput, value)));			
+ 			}
+ 		}
+ 		else
+ 			appendStringInfoString(&str, "null");
+ 	}
+ 	
+ 	result = cstring_to_text_with_len(str.data, str.len);
+ 	pfree(str.data);
+ 
+ 	PG_RETURN_TEXT_P(result);
+ }
+ 
+ 
+ /*
+  * Returns first n chars. When n is negative, then
+  * it returns chars from n+1 position.
+  */
+ Datum
+ stringfunc_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;
+ 		
+ 		len = mb_string_info(str, &sizes, &positions);
+ 		
+ 		if (n > 0)
+ 		{
+ 			n = n > len ? len : n;
+ 			result = cstring_to_text_with_len(p, positions[n - 1] + sizes[n - 1]);
+ 		}
+ 		else
+ 		{
+ 			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
+ stringfunc_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);
+ 		
+ 		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
+ stringfunc_rvrs(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);
+ 		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);
+ }
+ 
+ /*
+  * Convert C string to JSON string
+  */
+ static char *
+ json_string(char *str)
+ {
+ 	char	len = strlen(str);
+ 	char		*result, *wc;
+ 	
+ 	wc = result = palloc(len * 2 + 1);
+ 	while (*str != '\0')
+ 	{
+ 		char	c = *str++;
+ 		
+ 		switch (c)
+ 		{
+ 			case '\t':
+ 				*wc++ = '\\';
+ 				*wc++ = 't';
+ 				break;
+ 			case '\b':
+ 				*wc++ = '\\';
+ 				*wc++ = 'b';
+ 				break;
+ 			case '\n':
+ 				*wc++ = '\\';
+ 				*wc++ = 'n';
+ 				break;
+ 			case '\r':
+ 				*wc++ = '\\';
+ 				*wc++ = 'r';
+ 				break;
+ 			case '\\':
+ 				*wc++ = '\\';
+ 				*wc++ = '\\';
+ 				break;
+ 			case '"':
+ 				*wc++ = '\\';
+ 				*wc++ = '"';
+ 				break;
+ 			default:
+ 				*wc++ = c;
+ 		}
+ 	}
+ 	*wc = '\0';
+ 	return result;
+ }
+ 
+ /*
+  * Returns length of string, size and position of every
+  * multibyte char in string.
+  */
+ static int
+ mb_string_info(text *str, char **sizes, int **positions)
+ {
+ 	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;
+ 	}
+ 
+ 	return cur_size;
+ }
*** ./contrib/stringfunc/uninstall_stringfunc.sql.orig	2010-03-05 15:05:47.260738126 +0100
--- ./contrib/stringfunc/uninstall_stringfunc.sql	2010-03-09 13:27:00.146096268 +0100
***************
*** 0 ****
--- 1,9 ----
+ DROP FUNCTION sprintf(fmt text, VARIADIC args "any");
+ DROP FUNCTION sprintf(fmt text);
+ DROP FUNCTION concat(VARIADIC args "any");
+ DROP FUNCTION concat_ws(separator text, VARIADIC args "any");
+ DROP FUNCTION concat_json(VARIADIC args "any");
+ DROP FUNCTION concat_sql(VARIADIC args "any");
+ DROP FUNCTION rvrs(str text);
+ DROP FUNCTION left(str text, n int);
+ DROP FUNCTION right(str text, n int);
*** ./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-03-05 14:28:25.807765920 +0100
***************
*** 1262,1267 ****
--- 1262,1270 ----
      <primary>encode</primary>
     </indexterm>
     <indexterm>
+     <primary>format</primary>
+    </indexterm>
+    <indexterm>
      <primary>initcap</primary>
     </indexterm>
     <indexterm>
***************
*** 1450,1455 ****
--- 1453,1473 ----
        </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>
*** ./doc/src/sgml/stringfunc.sgml.orig	2010-03-05 15:11:23.284738364 +0100
--- ./doc/src/sgml/stringfunc.sgml	2010-03-09 15:10:43.378094796 +0100
***************
*** 0 ****
--- 1,79 ----
+ <!-- $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(param1 [, param2 [,...]])</> a concation two or more strings
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>concat_ws(separator, param1 [, param2 [,...]])()</> a concation two 
+       or more strings. First parameter is used as separator.
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>concat_json(param1 [, param2 [,...]])</> a concation two or more
+       strings. Values are converted to JSON format.
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>concat_sql(param1 [, param2 [,...]])</> a concation two or more
+       strings. Values are converted to format of SQL constants.
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>rvrs(str)</> reverse a string
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>left(str, n)</> returns n chars from start. When n is negative, then 
+       returns chars without last n chars.
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>right(str, n)</> return last n chars. When n is negative, then 
+       returns chars without first n chars.
+     </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-03-05 13:08:46.299862167 +0100
***************
*** 3415,3417 ****
--- 3415,3504 ----
  	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")));
+ 
+ 			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
+ 				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")));
+ 
+ 	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-02-26 03:01:21.000000000 +0100
--- ./src/include/catalog/pg_proc.h	2010-03-05 14:03:17.995737254 +0100
***************
*** 2712,2717 ****
--- 2712,2721 ----
  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 = 3098 ( format		PGNSP PGUID 12 1 0 2276 f f f f f i 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 i 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-03-05 13:13:24.979862366 +0100
***************
*** 730,735 ****
--- 730,738 ----
  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);
+ 
  /* 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-03-05 14:09:47.000000000 +0100
***************
*** 51,53 ****
--- 51,76 ----
  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
+ select format('Hello',10);
+ ERROR:  too many parameters
*** ./src/test/regress/sql/text.sql.orig	2007-06-07 01:00:50.000000000 +0200
--- ./src/test/regress/sql/text.sql	2010-03-05 14:09:34.083738128 +0100
***************
*** 28,30 ****
--- 28,39 ----
  -- 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);
+ 
#2David E. Wheeler
david@kineticode.com
In reply to: Pavel Stehule (#1)
Re: patch (for 9.1) string functions

On Mar 9, 2010, at 6:30 AM, Pavel Stehule wrote:

this patch contains a string formatting function "format"

postgres=# select format('some message: % (current user: %)',
current_date, current_user);
format
------------------------------------------------
some message: 2010-03-09 (current user: pavel)
(1 row)

this patch add new contrib module string functions - contains mainly
sprintf function:

Seems useful. Add it to the CommitFest so it doesn't get lost?

https://commitfest.postgresql.org/action/commitfest_view?id=6

Best,

David

#3Pavel Stehule
pavel.stehule@gmail.com
In reply to: David E. Wheeler (#2)
Re: patch (for 9.1) string functions

2010/3/9 David E. Wheeler <david@kineticode.com>:

On Mar 9, 2010, at 6:30 AM, Pavel Stehule wrote:

this patch contains a string formatting function "format"

postgres=# select format('some message: % (current user: %)',
current_date, current_user);
                    format
------------------------------------------------
some message: 2010-03-09 (current user: pavel)
(1 row)

this patch add new contrib module string functions - contains mainly
sprintf function:

Seems useful. Add it to the CommitFest so it doesn't get lost?

 https://commitfest.postgresql.org/action/commitfest_view?id=6

https://commitfest.postgresql.org/action/patch_view?id=287

it is there

Pavel

Show quoted text

Best,

David

#4Yeb Havinga
yebhavinga@gmail.com
In reply to: Pavel Stehule (#1)
Re: patch (for 9.1) string functions

Pavel Stehule wrote:

Hello

this patch contains a string formatting function "format"

Hi Pavel,

This is supercool. Haven't tried it out yet but surely will, thanks!

Yeb Havinga

#5Merlin Moncure
mmoncure@gmail.com
In reply to: Pavel Stehule (#1)
Re: patch (for 9.1) string functions

On Tue, Mar 9, 2010 at 9:30 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

postgres=# select concat('ahaha',10,null,current_date, true);
        concat
------------------------
 ahaha,10,,2010-03-09,t

why are there commas in the output?

merlin

#6Pavel Stehule
pavel.stehule@gmail.com
In reply to: Merlin Moncure (#5)
Re: patch (for 9.1) string functions

2010/3/9 Merlin Moncure <mmoncure@gmail.com>:

On Tue, Mar 9, 2010 at 9:30 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

postgres=# select concat('ahaha',10,null,current_date, true);
        concat
------------------------
 ahaha,10,,2010-03-09,t

why are there commas in the output?

I though so comma can be default separator - but I see - it is wrong -
separator have to be empty string.

Pavel

Show quoted text

merlin

#7Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#6)
1 attachment(s)
Re: patch (for 9.1) string functions

updated version, concat function doesn't use separator

Pavel

2010/3/9 Pavel Stehule <pavel.stehule@gmail.com>:

Show quoted text

2010/3/9 Merlin Moncure <mmoncure@gmail.com>:

On Tue, Mar 9, 2010 at 9:30 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

postgres=# select concat('ahaha',10,null,current_date, true);
        concat
------------------------
 ahaha,10,,2010-03-09,t

why are there commas in the output?

I though so comma can be default separator - but I see - it is wrong -
separator have to be empty string.

Pavel

merlin

Attachments:

stringfunc.diffapplication/octet-stream; name=stringfunc.diffDownload
*** ./contrib/stringfunc/expected/stringfunc.out.orig	2010-03-09 13:28:17.082096569 +0100
--- ./contrib/stringfunc/expected/stringfunc.out	2010-03-09 19:44:22.672219719 +0100
***************
*** 0 ****
--- 1,160 ----
+ 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('%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(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+            concat           
+ ----------------------------
+  123hellotf03-09-2010
+ (1 row)
+ 
+ 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)
+ 
+ select concat_sql(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+            concat_sql           
+ --------------------------------
+  1,2,3,'hello',t,f,'03-09-2010'
+ (1 row)
+ 
+ select concat_json(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+               concat_json              
+ ---------------------------------------
+  1,2,3,"hello",true,false,"03-09-2010"
+ (1 row)
+ 
+ /*
+  * others tests
+  */
+ select rvrs('abcde');
+  rvrs  
+ -------
+  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)
+ 
*** ./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-03-09 14:02:05.999094713 +0100
***************
*** 0 ****
--- 1,44 ----
+ 
+ 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('%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(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ select concat_ws('#',1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ select concat_sql(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ select concat_json(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ 
+ /*
+  * others tests
+  */
+ select rvrs('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);
*** ./contrib/stringfunc/stringfunc.c.orig	2010-03-05 15:05:30.876738040 +0100
--- ./contrib/stringfunc/stringfunc.c	2010-03-09 19:43:56.480094774 +0100
***************
*** 0 ****
--- 1,1024 ----
+ #include "postgres.h"
+ #include "stdio.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_format(PG_FUNCTION_ARGS);
+ Datum	stringfunc_format_nv(PG_FUNCTION_ARGS);
+ Datum	stringfunc_concat(PG_FUNCTION_ARGS);
+ Datum	stringfunc_concat_ws(PG_FUNCTION_ARGS);
+ Datum	stringfunc_concat_json(PG_FUNCTION_ARGS);
+ Datum	stringfunc_concat_sql(PG_FUNCTION_ARGS);
+ Datum	stringfunc_left(PG_FUNCTION_ARGS);
+ Datum	stringfunc_right(PG_FUNCTION_ARGS);
+ Datum	stringfunc_left(PG_FUNCTION_ARGS);
+ Datum	stringfunc_rvrs(PG_FUNCTION_ARGS);
+ 
+ /*
+  * V1 registrations
+  */
+ PG_FUNCTION_INFO_V1(stringfunc_sprintf);
+ PG_FUNCTION_INFO_V1(stringfunc_sprintf_nv);
+ PG_FUNCTION_INFO_V1(stringfunc_format);
+ PG_FUNCTION_INFO_V1(stringfunc_format_nv);
+ PG_FUNCTION_INFO_V1(stringfunc_concat);
+ PG_FUNCTION_INFO_V1(stringfunc_concat_ws);
+ PG_FUNCTION_INFO_V1(stringfunc_concat_json);
+ PG_FUNCTION_INFO_V1(stringfunc_concat_sql);
+ PG_FUNCTION_INFO_V1(stringfunc_rvrs);
+ 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 char *json_string(char *str);
+ 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;
+ }
+ 
+ /*
+  * 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")));
+ 		
+ 		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")));
+ 		
+ 		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;
+ 			}
+ 
+ 			if (i >= PG_NARGS())
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 						 errmsg("too few parameters")));
+ 						 
+ 			cp = parsePlaceholder(cp, end_ptr, &pdesc, fmt);
+ 			i = setWidthAndPrecision(&pdesc, fcinfo, i);
+ 
+ 			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;
+ 							const char 		*format;
+ 							Oid                     typoutput;
+ 							bool            typIsVarlena;
+ 
+ 							getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 							target_value = OidOutputFunctionCall(typoutput, value);
+ 							
+ 							format = currentFormat(&format_str, &pdesc);
+ 
+ 							/* use wide chars if it is necessary */
+ 							if (pg_database_encoding_max_length() > 1)
+ 							{
+ 								wchar_t *wformat;
+ 								wchar_t	*wbuffer;
+ 								size_t 	fmtlen = (strlen(format) + 1) * sizeof(wchar_t);
+ 								size_t	len = strlen(target_value) + 1;
+ 								
+ 								wformat = palloc(fmtlen);
+ 								char2wchar(wformat, fmtlen, format, strlen(format));
+ 								wbuffer = palloc(len * sizeof(wchar_t));
+ 								
+ 								for (;;)
+ 								{
+ 									int	result;
+ 									
+ 									if ((pdesc.flags & stringfunc_WIDTH) && (pdesc.flags & stringfunc_PRECISION))
+ 										result = swprintf(wbuffer, len, wformat, pdesc.width, 
+ 															 pdesc.precision, target_value);
+ 									else if (pdesc.flags & stringfunc_WIDTH)
+ 										result = swprintf(wbuffer, len, wformat, pdesc.width, target_value);
+ 									else if (pdesc.flags & stringfunc_PRECISION)
+ 										result = swprintf(wbuffer, len, wformat, pdesc.precision, target_value);
+ 									else
+ 										result = swprintf(wbuffer, len, wformat, target_value);
+ 									
+ 									if (result != -1)
+ 									{
+ 										/* append result */
+ 										appendStringInfo(&str, "%ls", wbuffer);
+ 										break;
+ 									}
+ 									else
+ 									{
+ 										/* increase buffer size and repeat */
+ 										len *= 2;
+ 										if ((len * sizeof(pg_wchar)) > MaxAllocSize)
+ 											ereport(ERROR,
+ 												(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ 												 errmsg("out of memory")));
+ 												 
+ 										wbuffer = repalloc(wbuffer, len * sizeof(wchar_t) + 1);
+ 										/* continue */
+ 									}
+ 								}
+ 								
+ 								pfree(wbuffer);
+ 								pfree(wformat);
+ 							}
+ 							else
+ 							{
+ 								/* shortcut for one byte encoding */
+ 								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);
+ 								
+ 								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")));
+ 	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 to comma separated list. This function
+  * is NULL safe. NULL values are skipped.
+  */
+ Datum
+ stringfunc_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);
+ }
+ 
+ /*
+  * 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);
+ }
+ 
+ /*
+  * Concat string with respect to SQL format. This is NULL safe.
+  * NULLs values are transformated to "NULL" string.
+  */
+ Datum
+ stringfunc_concat_sql(PG_FUNCTION_ARGS)
+ {
+ 	StringInfoData		str;
+ 	text *result;
+ 	int	i;
+ 
+ 	/* 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 (i > 0)
+ 			appendStringInfoChar(&str, ',');
+ 
+ 		if (!PG_ARGISNULL(i))
+ 		{
+ 			Oid	valtype;
+ 			Datum	value;
+ 			Oid                     typoutput;
+ 			bool            typIsVarlena;
+ 			TYPCATEGORY	typcat;
+ 
+ 			/* append n-th value */
+ 			value = PG_GETARG_DATUM(i);
+ 			valtype = get_fn_expr_argtype(fcinfo->flinfo, i);
+ 			typcat = TypeCategory(valtype);
+ 
+ 			if (typcat == 'N' || typcat == 'B')
+ 			{
+ 				getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 				appendStringInfoString(&str, OidOutputFunctionCall(typoutput, value));
+ 			}
+ 			else
+ 			{
+ 				text	*txt;
+ 				text	*quoted_txt;
+ 			
+ 				getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 				
+ 				/* 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
+ 			appendStringInfoString(&str, "NULL");
+ 	}
+ 
+ 	result = cstring_to_text_with_len(str.data, str.len);
+ 	pfree(str.data);
+ 
+ 	PG_RETURN_TEXT_P(result);
+ }
+ 
+ 
+ /*
+  * Concat string with respect to JSON format. This is NULL safe.
+  * NULLs values are transformated to "null" string.
+  * JSON uses lowercase characters for boolean constants - see www.json.org
+  */
+ Datum
+ stringfunc_concat_json(PG_FUNCTION_ARGS)
+ {
+ 	StringInfoData	str;
+ 	text	*result;
+ 	int i;
+ 
+ 	/* 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 (i > 0)
+ 			appendStringInfoChar(&str, ',');
+ 
+ 		if (!PG_ARGISNULL(i))
+ 		{
+ 			Oid	valtype;
+ 			Datum	value;
+ 			Oid                     typoutput;
+ 			bool            typIsVarlena;
+ 			TYPCATEGORY	typcat;
+ 
+ 			/* append n-th value */
+ 			value = PG_GETARG_DATUM(i);
+ 			valtype = get_fn_expr_argtype(fcinfo->flinfo, i);
+ 			typcat = TypeCategory(valtype);
+ 
+ 			if (typcat == 'N')
+ 			{
+ 				getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 				appendStringInfoString(&str, OidOutputFunctionCall(typoutput, value));
+ 			} else if (typcat == 'B')
+ 			{
+ 				bool	bvalue = PG_GETARG_BOOL(i);
+ 				
+ 				appendStringInfoString(&str, bvalue ? "true" : "false");
+ 			}
+ 			else
+ 			{
+ 				getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 				appendStringInfo(&str, "\"%s\"", json_string(OidOutputFunctionCall(typoutput, value)));			
+ 			}
+ 		}
+ 		else
+ 			appendStringInfoString(&str, "null");
+ 	}
+ 	
+ 	result = cstring_to_text_with_len(str.data, str.len);
+ 	pfree(str.data);
+ 
+ 	PG_RETURN_TEXT_P(result);
+ }
+ 
+ 
+ /*
+  * Returns first n chars. When n is negative, then
+  * it returns chars from n+1 position.
+  */
+ Datum
+ stringfunc_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;
+ 		
+ 		len = mb_string_info(str, &sizes, &positions);
+ 		
+ 		if (n > 0)
+ 		{
+ 			n = n > len ? len : n;
+ 			result = cstring_to_text_with_len(p, positions[n - 1] + sizes[n - 1]);
+ 		}
+ 		else
+ 		{
+ 			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
+ stringfunc_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);
+ 		
+ 		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
+ stringfunc_rvrs(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);
+ 		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);
+ }
+ 
+ /*
+  * Convert C string to JSON string
+  */
+ static char *
+ json_string(char *str)
+ {
+ 	char	len = strlen(str);
+ 	char		*result, *wc;
+ 	
+ 	wc = result = palloc(len * 2 + 1);
+ 	while (*str != '\0')
+ 	{
+ 		char	c = *str++;
+ 		
+ 		switch (c)
+ 		{
+ 			case '\t':
+ 				*wc++ = '\\';
+ 				*wc++ = 't';
+ 				break;
+ 			case '\b':
+ 				*wc++ = '\\';
+ 				*wc++ = 'b';
+ 				break;
+ 			case '\n':
+ 				*wc++ = '\\';
+ 				*wc++ = 'n';
+ 				break;
+ 			case '\r':
+ 				*wc++ = '\\';
+ 				*wc++ = 'r';
+ 				break;
+ 			case '\\':
+ 				*wc++ = '\\';
+ 				*wc++ = '\\';
+ 				break;
+ 			case '"':
+ 				*wc++ = '\\';
+ 				*wc++ = '"';
+ 				break;
+ 			default:
+ 				*wc++ = c;
+ 		}
+ 	}
+ 	*wc = '\0';
+ 	return result;
+ }
+ 
+ /*
+  * Returns length of string, size and position of every
+  * multibyte char in string.
+  */
+ static int
+ mb_string_info(text *str, char **sizes, int **positions)
+ {
+ 	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;
+ 	}
+ 
+ 	return cur_size;
+ }
*** ./contrib/stringfunc/uninstall_stringfunc.sql.orig	2010-03-05 15:05:47.260738126 +0100
--- ./contrib/stringfunc/uninstall_stringfunc.sql	2010-03-09 13:27:00.146096268 +0100
***************
*** 0 ****
--- 1,9 ----
+ DROP FUNCTION sprintf(fmt text, VARIADIC args "any");
+ DROP FUNCTION sprintf(fmt text);
+ DROP FUNCTION concat(VARIADIC args "any");
+ DROP FUNCTION concat_ws(separator text, VARIADIC args "any");
+ DROP FUNCTION concat_json(VARIADIC args "any");
+ DROP FUNCTION concat_sql(VARIADIC args "any");
+ DROP FUNCTION rvrs(str text);
+ DROP FUNCTION left(str text, n int);
+ DROP FUNCTION right(str text, n int);
*** ./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-03-05 14:28:25.807765920 +0100
***************
*** 1262,1267 ****
--- 1262,1270 ----
      <primary>encode</primary>
     </indexterm>
     <indexterm>
+     <primary>format</primary>
+    </indexterm>
+    <indexterm>
      <primary>initcap</primary>
     </indexterm>
     <indexterm>
***************
*** 1450,1455 ****
--- 1453,1473 ----
        </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>
*** ./doc/src/sgml/stringfunc.sgml.orig	2010-03-05 15:11:23.284738364 +0100
--- ./doc/src/sgml/stringfunc.sgml	2010-03-09 15:10:43.378094796 +0100
***************
*** 0 ****
--- 1,79 ----
+ <!-- $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(param1 [, param2 [,...]])</> a concation two or more strings
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>concat_ws(separator, param1 [, param2 [,...]])()</> a concation two 
+       or more strings. First parameter is used as separator.
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>concat_json(param1 [, param2 [,...]])</> a concation two or more
+       strings. Values are converted to JSON format.
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>concat_sql(param1 [, param2 [,...]])</> a concation two or more
+       strings. Values are converted to format of SQL constants.
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>rvrs(str)</> reverse a string
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>left(str, n)</> returns n chars from start. When n is negative, then 
+       returns chars without last n chars.
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>right(str, n)</> return last n chars. When n is negative, then 
+       returns chars without first n chars.
+     </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-03-05 13:08:46.299862167 +0100
***************
*** 3415,3417 ****
--- 3415,3504 ----
  	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")));
+ 
+ 			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
+ 				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")));
+ 
+ 	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-02-26 03:01:21.000000000 +0100
--- ./src/include/catalog/pg_proc.h	2010-03-05 14:03:17.995737254 +0100
***************
*** 2712,2717 ****
--- 2712,2721 ----
  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 = 3098 ( format		PGNSP PGUID 12 1 0 2276 f f f f f i 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 i 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-03-05 13:13:24.979862366 +0100
***************
*** 730,735 ****
--- 730,738 ----
  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);
+ 
  /* 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-03-05 14:09:47.000000000 +0100
***************
*** 51,53 ****
--- 51,76 ----
  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
+ select format('Hello',10);
+ ERROR:  too many parameters
*** ./src/test/regress/sql/text.sql.orig	2007-06-07 01:00:50.000000000 +0200
--- ./src/test/regress/sql/text.sql	2010-03-05 14:09:34.083738128 +0100
***************
*** 28,30 ****
--- 28,39 ----
  -- 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);
+ 
#8Merlin Moncure
mmoncure@gmail.com
In reply to: Pavel Stehule (#7)
Re: patch (for 9.1) string functions

On Tue, Mar 9, 2010 at 1:45 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

updated version, concat function doesn't use separator

btw...very cool stuff. I took a brief look at the sprintf
implementation. The main switch:
switch (pdesc.field_type)

It looks like format codes we choose not to support (like %p) are
silently ignored. Is this the correct behavior?

merlin

#9Pavel Stehule
pavel.stehule@gmail.com
In reply to: Merlin Moncure (#8)
Re: patch (for 9.1) string functions

2010/3/9 Merlin Moncure <mmoncure@gmail.com>:

On Tue, Mar 9, 2010 at 1:45 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

updated version, concat function doesn't use separator

btw...very cool stuff.  I took a brief look at the sprintf
implementation.  The main switch:
switch (pdesc.field_type)

It looks like format codes we choose not to support (like %p) are
silently ignored.  Is this the correct behavior?

it could be enhanced. sprintf is little bit baroque function and I am
not able to take all details. Please, add comment with proposals for
change.

Pavel

Show quoted text

merlin

#10Takahiro Itagaki
itagaki.takahiro@oss.ntt.co.jp
In reply to: Pavel Stehule (#7)
Re: patch (for 9.1) string functions

Pavel Stehule <pavel.stehule@gmail.com> wrote:

updated version, concat function doesn't use separator

BTW, didn't you forget stringfunc.sql.in for contrib/stringfunc ?
So, I have not check stringfunc module yet.

I reviewed your patch, and format() in the core is almost ok. It's very cool!
On the other hand, contrib/stringfunc tries to implement safe-sprintf. It's
very complex, and I have questions about multi-byte character handling in it.

* How to print NULL value.
format() function prints NULL as "NULL", but RAISE statement in PL/pgSQL
does as "<NULL>". Do we need the same result for them?

postgres=# SELECT format('% vs %', 'NULL', NULL);
format
--------------
NULL vs NULL
(1 row)

postgres=# DO $$ BEGIN RAISE NOTICE '% vs %', 'NULL', NULL; END; $$;
NOTICE: NULL vs <NULL>
DO

* Error messages: "too few/many parameters"
For the same reason, "too few/many parameters specified for format()"
might be better for the messages.

For RAISE in PL/pgSQL:
ERROR: too few parameters specified for RAISE
ERROR: too many parameters specified for RAISE

* Why do you need convert multi-byte characters to wide char?
Length specifier in stringfunc_sprintf() means "character length".
But is pg_encoding_mbcliplen() enough for the purpose?

* Character-length vs. disp-length in length specifier for sprintf()
For example, '%10s' for sprintf() means "10 characters" in the code.
But there might be usages to format text values for display. In such
case, display length might be better for the length specifier.
How about having both "s" and "S"?
"%10s" -- 10 characters
"%10S" -- 10 disp length; we could use pg_dsplen() for the purpse.

Regards,
---
Takahiro Itagaki
NTT Open Source Software Center

#11Pavel Stehule
pavel.stehule@gmail.com
In reply to: Takahiro Itagaki (#10)
1 attachment(s)
Re: patch (for 9.1) string functions

Hello

2010/7/8 Takahiro Itagaki <itagaki.takahiro@oss.ntt.co.jp>:

Pavel Stehule <pavel.stehule@gmail.com> wrote:

updated version, concat function doesn't use separator

BTW, didn't you forget stringfunc.sql.in for contrib/stringfunc ?
So, I have not check stringfunc module yet.

sorry, attached fixed patch

I reviewed your patch, and format() in the core is almost ok. It's very cool!
On the other hand, contrib/stringfunc tries to implement safe-sprintf. It's
very complex, and I have questions about multi-byte character handling in it.

I use same mechanism as RAISE statement does. And it working for mer

postgres=# select sprintf('žlutý%dkůň',10);
sprintf
------------
žlutý10kůň
(1 row)

Time: 0,647 ms

postgres=# select sprintf('%s žlutý kůň','příliš');
sprintf
------------------
příliš žlutý kůň
(1 row)

Time: 11,017 ms
postgres=# select sprintf('%10s žlutý kůň','příliš');
sprintf
----------------------
příliš žlutý kůň
(1 row)

Time: 0,439 ms

* How to print NULL value.
format() function prints NULL as "NULL", but RAISE statement in PL/pgSQL
does as "<NULL>". Do we need the same result for them?

I prefer just NULL. You can add "<" and ">" simple if you want. But
removing is little bit dificult.

postgres=# select sprintf('%s', coalesce(NULL, '<NULL>'));
sprintf
---------
<NULL>
(1 row)

maybe some GUC variable

stringfunc.null_string = '<NULL>' in future??

   postgres=# SELECT format('% vs %', 'NULL', NULL);
       format
   --------------
    NULL vs NULL
   (1 row)

   postgres=# DO $$ BEGIN RAISE NOTICE '% vs %', 'NULL', NULL; END; $$;
   NOTICE:  NULL vs <NULL>
   DO

* Error messages: "too few/many parameters"
 For the same reason, "too few/many parameters specified for format()"
 might be better for the messages.

 For RAISE in PL/pgSQL:
   ERROR:  too few parameters specified for RAISE
   ERROR:  too many parameters specified for RAISE

ook, I agree

* Why do you need convert multi-byte characters to wide char?
Length specifier in stringfunc_sprintf() means "character length".
But is pg_encoding_mbcliplen() enough for the purpose?

No, I need it. I use a swprintf function - for output of formated
strings - and there are not some sprintf function for multibyte chars
:(. Without this function I don't need a multibyte->widechars
conversion, but sprintf function will be much more larger and complex.

* Character-length vs. disp-length in length specifier for sprintf()
For example, '%10s' for sprintf() means "10 characters" in the code.
But there might be usages to format text values for display. In such
case, display length might be better for the length specifier.
How about having both "s" and "S"?
   "%10s" -- 10 characters
   "%10S" -- 10 disp length; we could use pg_dsplen() for the purpse.

it is independent, because I use swprintf function

postgres=# select length(sprintf('%5s', 'ščř'));
length
--------
5
(1 row)

Time: 45,485 ms
postgres=# select length(sprintf('%5s', 'abc'));
length
--------
5
(1 row)

Time: 0,499 ms

so it is equal to using a pg_dsplen()

probably original one byte behave have sense for bytea data type. But
I am not sure if we would to complicate this function for binary data.

Regards,

Thank you very much for review

Pavel Stehule

Show quoted text

---
Takahiro Itagaki
NTT Open Source Software Center

Attachments:

stringfunc.diffapplication/octet-stream; name=stringfunc.diffDownload
*** ./contrib/stringfunc/expected/stringfunc.out.orig	2010-03-09 13:28:17.082096569 +0100
--- ./contrib/stringfunc/expected/stringfunc.out	2010-03-09 19:44:22.672219719 +0100
***************
*** 0 ****
--- 1,160 ----
+ 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('%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(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+            concat           
+ ----------------------------
+  123hellotf03-09-2010
+ (1 row)
+ 
+ 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)
+ 
+ select concat_sql(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+            concat_sql           
+ --------------------------------
+  1,2,3,'hello',t,f,'03-09-2010'
+ (1 row)
+ 
+ select concat_json(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+               concat_json              
+ ---------------------------------------
+  1,2,3,"hello",true,false,"03-09-2010"
+ (1 row)
+ 
+ /*
+  * others tests
+  */
+ select rvrs('abcde');
+  rvrs  
+ -------
+  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)
+ 
*** ./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-03-09 14:02:05.999094713 +0100
***************
*** 0 ****
--- 1,44 ----
+ 
+ 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('%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(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ select concat_ws('#',1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ select concat_sql(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ select concat_json(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ 
+ /*
+  * others tests
+  */
+ select rvrs('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);
*** ./contrib/stringfunc/stringfunc.c.orig	2010-03-05 15:05:30.876738040 +0100
--- ./contrib/stringfunc/stringfunc.c	2010-07-08 13:04:13.458422094 +0200
***************
*** 0 ****
--- 1,1020 ----
+ #include "postgres.h"
+ #include "stdio.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_concat_json(PG_FUNCTION_ARGS);
+ Datum	stringfunc_concat_sql(PG_FUNCTION_ARGS);
+ Datum	stringfunc_left(PG_FUNCTION_ARGS);
+ Datum	stringfunc_right(PG_FUNCTION_ARGS);
+ Datum	stringfunc_left(PG_FUNCTION_ARGS);
+ Datum	stringfunc_rvrs(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_concat_json);
+ PG_FUNCTION_INFO_V1(stringfunc_concat_sql);
+ PG_FUNCTION_INFO_V1(stringfunc_rvrs);
+ 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 char *json_string(char *str);
+ 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;
+ }
+ 
+ /*
+  * 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;
+ 			}
+ 
+ 			if (i >= PG_NARGS())
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 						 errmsg("too few parameters specified for printf function")));
+ 						 
+ 			cp = parsePlaceholder(cp, end_ptr, &pdesc, fmt);
+ 			i = setWidthAndPrecision(&pdesc, fcinfo, i);
+ 
+ 			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;
+ 							const char 		*format;
+ 							Oid                     typoutput;
+ 							bool            typIsVarlena;
+ 
+ 							getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 							target_value = OidOutputFunctionCall(typoutput, value);
+ 							
+ 							format = currentFormat(&format_str, &pdesc);
+ 
+ 							/* use wide chars if it is necessary */
+ 							if (pg_database_encoding_max_length() > 1)
+ 							{
+ 								wchar_t *wformat;
+ 								wchar_t	*wbuffer;
+ 								size_t 	fmtlen = (strlen(format) + 1) * sizeof(wchar_t);
+ 								size_t	len = strlen(target_value) + 1;
+ 								
+ 								wformat = palloc(fmtlen);
+ 								char2wchar(wformat, fmtlen, format, strlen(format));
+ 								wbuffer = palloc(len * sizeof(wchar_t));
+ 								
+ 								for (;;)
+ 								{
+ 									int	result;
+ 									
+ 									if ((pdesc.flags & stringfunc_WIDTH) && (pdesc.flags & stringfunc_PRECISION))
+ 										result = swprintf(wbuffer, len, wformat, pdesc.width, 
+ 															 pdesc.precision, target_value);
+ 									else if (pdesc.flags & stringfunc_WIDTH)
+ 										result = swprintf(wbuffer, len, wformat, pdesc.width, target_value);
+ 									else if (pdesc.flags & stringfunc_PRECISION)
+ 										result = swprintf(wbuffer, len, wformat, pdesc.precision, target_value);
+ 									else
+ 										result = swprintf(wbuffer, len, wformat, target_value);
+ 									
+ 									if (result != -1)
+ 									{
+ 										/* append result */
+ 										appendStringInfo(&str, "%ls", wbuffer);
+ 										break;
+ 									}
+ 									else
+ 									{
+ 										/* increase buffer size and repeat */
+ 										len *= 2;
+ 										if ((len * sizeof(pg_wchar)) > MaxAllocSize)
+ 											ereport(ERROR,
+ 												(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ 												 errmsg("out of memory")));
+ 												 
+ 										wbuffer = repalloc(wbuffer, len * sizeof(wchar_t) + 1);
+ 										/* continue */
+ 									}
+ 								}
+ 								
+ 								pfree(wbuffer);
+ 								pfree(wformat);
+ 							}
+ 							else
+ 							{
+ 								/* shortcut for one byte encoding */
+ 								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);
+ 								
+ 								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")));
+ 	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 to comma separated list. This function
+  * is NULL safe. NULL values are skipped.
+  */
+ Datum
+ stringfunc_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);
+ }
+ 
+ /*
+  * 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);
+ }
+ 
+ /*
+  * Concat string with respect to SQL format. This is NULL safe.
+  * NULLs values are transformated to "NULL" string.
+  */
+ Datum
+ stringfunc_concat_sql(PG_FUNCTION_ARGS)
+ {
+ 	StringInfoData		str;
+ 	text *result;
+ 	int	i;
+ 
+ 	/* 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 (i > 0)
+ 			appendStringInfoChar(&str, ',');
+ 
+ 		if (!PG_ARGISNULL(i))
+ 		{
+ 			Oid	valtype;
+ 			Datum	value;
+ 			Oid                     typoutput;
+ 			bool            typIsVarlena;
+ 			TYPCATEGORY	typcat;
+ 
+ 			/* append n-th value */
+ 			value = PG_GETARG_DATUM(i);
+ 			valtype = get_fn_expr_argtype(fcinfo->flinfo, i);
+ 			typcat = TypeCategory(valtype);
+ 
+ 			if (typcat == 'N' || typcat == 'B')
+ 			{
+ 				getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 				appendStringInfoString(&str, OidOutputFunctionCall(typoutput, value));
+ 			}
+ 			else
+ 			{
+ 				text	*txt;
+ 				text	*quoted_txt;
+ 			
+ 				getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 				
+ 				/* 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
+ 			appendStringInfoString(&str, "NULL");
+ 	}
+ 
+ 	result = cstring_to_text_with_len(str.data, str.len);
+ 	pfree(str.data);
+ 
+ 	PG_RETURN_TEXT_P(result);
+ }
+ 
+ 
+ /*
+  * Concat string with respect to JSON format. This is NULL safe.
+  * NULLs values are transformated to "null" string.
+  * JSON uses lowercase characters for boolean constants - see www.json.org
+  */
+ Datum
+ stringfunc_concat_json(PG_FUNCTION_ARGS)
+ {
+ 	StringInfoData	str;
+ 	text	*result;
+ 	int i;
+ 
+ 	/* 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 (i > 0)
+ 			appendStringInfoChar(&str, ',');
+ 
+ 		if (!PG_ARGISNULL(i))
+ 		{
+ 			Oid	valtype;
+ 			Datum	value;
+ 			Oid                     typoutput;
+ 			bool            typIsVarlena;
+ 			TYPCATEGORY	typcat;
+ 
+ 			/* append n-th value */
+ 			value = PG_GETARG_DATUM(i);
+ 			valtype = get_fn_expr_argtype(fcinfo->flinfo, i);
+ 			typcat = TypeCategory(valtype);
+ 
+ 			if (typcat == 'N')
+ 			{
+ 				getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 				appendStringInfoString(&str, OidOutputFunctionCall(typoutput, value));
+ 			} else if (typcat == 'B')
+ 			{
+ 				bool	bvalue = PG_GETARG_BOOL(i);
+ 				
+ 				appendStringInfoString(&str, bvalue ? "true" : "false");
+ 			}
+ 			else
+ 			{
+ 				getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 				appendStringInfo(&str, "\"%s\"", json_string(OidOutputFunctionCall(typoutput, value)));			
+ 			}
+ 		}
+ 		else
+ 			appendStringInfoString(&str, "null");
+ 	}
+ 	
+ 	result = cstring_to_text_with_len(str.data, str.len);
+ 	pfree(str.data);
+ 
+ 	PG_RETURN_TEXT_P(result);
+ }
+ 
+ 
+ /*
+  * Returns first n chars. When n is negative, then
+  * it returns chars from n+1 position.
+  */
+ Datum
+ stringfunc_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;
+ 		
+ 		len = mb_string_info(str, &sizes, &positions);
+ 		
+ 		if (n > 0)
+ 		{
+ 			n = n > len ? len : n;
+ 			result = cstring_to_text_with_len(p, positions[n - 1] + sizes[n - 1]);
+ 		}
+ 		else
+ 		{
+ 			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
+ stringfunc_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);
+ 		
+ 		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
+ stringfunc_rvrs(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);
+ 		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);
+ }
+ 
+ /*
+  * Convert C string to JSON string
+  */
+ static char *
+ json_string(char *str)
+ {
+ 	char	len = strlen(str);
+ 	char		*result, *wc;
+ 	
+ 	wc = result = palloc(len * 2 + 1);
+ 	while (*str != '\0')
+ 	{
+ 		char	c = *str++;
+ 		
+ 		switch (c)
+ 		{
+ 			case '\t':
+ 				*wc++ = '\\';
+ 				*wc++ = 't';
+ 				break;
+ 			case '\b':
+ 				*wc++ = '\\';
+ 				*wc++ = 'b';
+ 				break;
+ 			case '\n':
+ 				*wc++ = '\\';
+ 				*wc++ = 'n';
+ 				break;
+ 			case '\r':
+ 				*wc++ = '\\';
+ 				*wc++ = 'r';
+ 				break;
+ 			case '\\':
+ 				*wc++ = '\\';
+ 				*wc++ = '\\';
+ 				break;
+ 			case '"':
+ 				*wc++ = '\\';
+ 				*wc++ = '"';
+ 				break;
+ 			default:
+ 				*wc++ = c;
+ 		}
+ 	}
+ 	*wc = '\0';
+ 	return result;
+ }
+ 
+ /*
+  * Returns length of string, size and position of every
+  * multibyte char in string.
+  */
+ static int
+ mb_string_info(text *str, char **sizes, int **positions)
+ {
+ 	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;
+ 	}
+ 
+ 	return cur_size;
+ }
*** ./contrib/stringfunc/stringfunc.sql.in.orig	2010-07-08 11:27:31.877299385 +0200
--- ./contrib/stringfunc/stringfunc.sql.in	2010-03-09 13:27:16.618094628 +0100
***************
*** 0 ****
--- 1,44 ----
+ CREATE OR REPLACE FUNCTION sprintf(fmt text, VARIADIC args "any")
+ RETURNS text 
+ AS 'MODULE_PATHNAME','stringfunc_sprintf'
+ LANGUAGE C IMMUTABLE;
+ 
+ CREATE OR REPLACE FUNCTION sprintf(fmt text)
+ RETURNS text 
+ AS 'MODULE_PATHNAME','stringfunc_sprintf_nv'
+ LANGUAGE C IMMUTABLE;
+ 
+ CREATE OR REPLACE FUNCTION concat(VARIADIC args "any")
+ RETURNS text 
+ AS 'MODULE_PATHNAME','stringfunc_concat'
+ LANGUAGE C IMMUTABLE;
+ 
+ CREATE OR REPLACE FUNCTION concat_ws(separator text, VARIADIC args "any")
+ RETURNS text 
+ AS 'MODULE_PATHNAME','stringfunc_concat_ws'
+ LANGUAGE C IMMUTABLE;
+ 
+ CREATE OR REPLACE FUNCTION concat_json(VARIADIC args "any")
+ RETURNS text 
+ AS 'MODULE_PATHNAME','stringfunc_concat_json'
+ LANGUAGE C IMMUTABLE;
+ 
+ CREATE OR REPLACE FUNCTION concat_sql(VARIADIC args "any")
+ RETURNS text 
+ AS 'MODULE_PATHNAME','stringfunc_concat_sql'
+ LANGUAGE C IMMUTABLE;
+ 
+ CREATE OR REPLACE FUNCTION rvrs(str text)
+ RETURNS text 
+ AS 'MODULE_PATHNAME','stringfunc_rvrs'
+ LANGUAGE C IMMUTABLE STRICT;
+ 
+ CREATE OR REPLACE FUNCTION left(str text, n int)
+ RETURNS text 
+ AS 'MODULE_PATHNAME','stringfunc_left'
+ LANGUAGE C IMMUTABLE STRICT;
+ 
+ CREATE OR REPLACE FUNCTION right(str text, n int)
+ RETURNS text 
+ AS 'MODULE_PATHNAME','stringfunc_right'
+ LANGUAGE C IMMUTABLE STRICT;
*** ./contrib/stringfunc/uninstall_stringfunc.sql.orig	2010-03-05 15:05:47.260738126 +0100
--- ./contrib/stringfunc/uninstall_stringfunc.sql	2010-03-09 13:27:00.146096268 +0100
***************
*** 0 ****
--- 1,9 ----
+ DROP FUNCTION sprintf(fmt text, VARIADIC args "any");
+ DROP FUNCTION sprintf(fmt text);
+ DROP FUNCTION concat(VARIADIC args "any");
+ DROP FUNCTION concat_ws(separator text, VARIADIC args "any");
+ DROP FUNCTION concat_json(VARIADIC args "any");
+ DROP FUNCTION concat_sql(VARIADIC args "any");
+ DROP FUNCTION rvrs(str text);
+ DROP FUNCTION left(str text, n int);
+ DROP FUNCTION right(str text, n int);
*** ./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-03-05 14:28:25.807765920 +0100
***************
*** 1262,1267 ****
--- 1262,1270 ----
      <primary>encode</primary>
     </indexterm>
     <indexterm>
+     <primary>format</primary>
+    </indexterm>
+    <indexterm>
      <primary>initcap</primary>
     </indexterm>
     <indexterm>
***************
*** 1450,1455 ****
--- 1453,1473 ----
        </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>
*** ./doc/src/sgml/stringfunc.sgml.orig	2010-03-05 15:11:23.284738364 +0100
--- ./doc/src/sgml/stringfunc.sgml	2010-03-09 15:10:43.378094796 +0100
***************
*** 0 ****
--- 1,79 ----
+ <!-- $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(param1 [, param2 [,...]])</> a concation two or more strings
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>concat_ws(separator, param1 [, param2 [,...]])()</> a concation two 
+       or more strings. First parameter is used as separator.
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>concat_json(param1 [, param2 [,...]])</> a concation two or more
+       strings. Values are converted to JSON format.
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>concat_sql(param1 [, param2 [,...]])</> a concation two or more
+       strings. Values are converted to format of SQL constants.
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>rvrs(str)</> reverse a string
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>left(str, n)</> returns n chars from start. When n is negative, then 
+       returns chars without last n chars.
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>right(str, n)</> return last n chars. When n is negative, then 
+       returns chars without first n chars.
+     </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-08 13:06:52.372551690 +0200
***************
*** 3415,3417 ****
--- 3415,3504 ----
  	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
+ 				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")));
+ 
+ 	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-02-26 03:01:21.000000000 +0100
--- ./src/include/catalog/pg_proc.h	2010-03-05 14:03:17.995737254 +0100
***************
*** 2712,2717 ****
--- 2712,2721 ----
  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 = 3098 ( format		PGNSP PGUID 12 1 0 2276 f f f f f i 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 i 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-03-05 13:13:24.979862366 +0100
***************
*** 730,735 ****
--- 730,738 ----
  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);
+ 
  /* 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-03-05 14:09:47.000000000 +0100
***************
*** 51,53 ****
--- 51,76 ----
  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
+ select format('Hello',10);
+ ERROR:  too many parameters
*** ./src/test/regress/sql/text.sql.orig	2007-06-07 01:00:50.000000000 +0200
--- ./src/test/regress/sql/text.sql	2010-03-05 14:09:34.083738128 +0100
***************
*** 28,30 ****
--- 28,39 ----
  -- 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);
+ 
#12Takahiro Itagaki
itagaki.takahiro@gmail.com
In reply to: Pavel Stehule (#11)
Re: patch (for 9.1) string functions

2010/7/8 Pavel Stehule <pavel.stehule@gmail.com>:

sorry, attached fixed patch

Make installcheck for contrib/stringfunc is broken.
Please run regression test with --enable-cassert build.
test stringfunc ... TRAP:
FailedAssertion("!(!lc_ctype_is_c())", File: "mbutils.c", Line: 715)
LOG: server process (PID 15121) was terminated by signal 6: Aborted

This patch contains several functions.
- format(fmt text, VARIADIC args "any")
- sprintf(fmt text, VARIADIC args "any")
- concat(VARIADIC args "any")
- concat_ws(separator text, VARIADIC args "any")
- concat_json(VARIADIC args "any")
- concat_sql(VARIADIC args "any")
- rvrs(str text)
- left(str text, n int)
- right(str text, n int)

The first one is in the core, and others are in contrib/stringfunc.
But I think almost
all of them should be in the core, because users want to write portable SQLs.
Contrib modules are not always available. Note that concat() is
supported by Oracle,
MySQL, and DB2. Also left() and right() are supported by MySQL, DB2,
and SQL Server.

Functions that depend on GUC settings should be marked as VOLATILE
instead of IMMUTABLE. I think format(), sprintf(), and all of
concat()s should be
volatile because at least timestamp depends on datestyle parameter.

concat_ws() and rvrs() should be renamed to non-abbreviated forms.
How about concat_with_sep() and reverse() ?

I think we should avoid concat_json() at the moment because there is another
development project for JSON support. The result type will be JOIN type rather
than text then.

I'm not sure usefulness of concat_sql(). Why don't you just quote all values
with quotes and separate them with comma?

format() function prints NULL as "NULL", but RAISE statement in PL/pgSQL
does as "<NULL>".

I prefer just NULL.
maybe some GUC variable
stringfunc.null_string = '<NULL>' in future??

We have some choices for NULL representation. For example, empty string,
NULL, <NULL>, or (null) . What will be our choice? Each of them looks
equally reasonable for me. GUC idea is also good because we need to
mark format() as VOLATILE anyway. We have nothing to lose.

---
Takahiro Itagaki

#13Pavel Stehule
pavel.stehule@gmail.com
In reply to: Takahiro Itagaki (#12)
Re: patch (for 9.1) string functions

hello

2010/7/9 Takahiro Itagaki <itagaki.takahiro@gmail.com>:

2010/7/8 Pavel Stehule <pavel.stehule@gmail.com>:

sorry, attached fixed patch

Make installcheck for contrib/stringfunc is broken.
Please run regression test with --enable-cassert build.
 test stringfunc           ... TRAP:
FailedAssertion("!(!lc_ctype_is_c())", File: "mbutils.c", Line: 715)
 LOG:  server process (PID 15121) was terminated by signal 6: Aborted

This patch contains several functions.
- format(fmt text, VARIADIC args "any")
- sprintf(fmt text, VARIADIC args "any")
- concat(VARIADIC args "any")
- concat_ws(separator text, VARIADIC args "any")
- concat_json(VARIADIC args "any")
- concat_sql(VARIADIC args "any")
- rvrs(str text)
- left(str text, n int)
- right(str text, n int)

The first one is in the core, and others are in contrib/stringfunc.
But I think almost
all of them should be in the core, because users want to write portable SQLs.
Contrib modules are not always available.  Note that concat() is
supported by Oracle,
MySQL, and DB2. Also left() and right() are supported by MySQL, DB2,
and SQL Server.

Functions that depend on GUC settings should be marked as VOLATILE
instead of IMMUTABLE. I think format(), sprintf(), and all of
concat()s should be
volatile because at least timestamp depends on datestyle parameter.

ok, I'll fix it

concat_ws() and rvrs() should be renamed to non-abbreviated forms.
How about concat_with_sep() and reverse() ?

I used a well known names - concat_ws (MySQL) and rvrs (Oracle rdbms),
I like concat_ws - concat_with_sep is maybe too long. rvrs is too
short, so I'll rename it to reverse - ok?

I think we should avoid concat_json() at the moment because there is another
development project for JSON support. The result type will be JOIN type rather
than text then.

ok

I'm not sure usefulness of concat_sql(). Why don't you just quote all values
with quotes and separate them with comma?

concat_xxx functions are helpers to serialisation. So when when you
would to generate INSERT statements for some export, and you cannot
use a COPY statement, you can do

FOR r IN
SELECT ....
LOOP
RETURN NEXT 'INSERT INTO tab(..) VALUES (' || concat_sql(r.a, r.b, r.c, ... )
END LOOP;
RETURN;

you don't need to solve anything and output is well formated SQL. Some
databases dislike quoted numeric values - and quoted nums can be
sonfusing

format() function prints NULL as "NULL", but RAISE statement in PL/pgSQL
does as "<NULL>".

I prefer just NULL.
maybe some GUC variable
stringfunc.null_string = '<NULL>' in future??

We have some choices for NULL representation. For example, empty string,
NULL, <NULL>, or (null) . What will be our choice?   Each of them looks
equally reasonable for me. GUC idea is also good because we need to
mark format() as VOLATILE anyway. We have nothing to lose.

Can ve to solve it other patch? I know to aversion core hackers to new
GUC. Now I propose just "NULL". The GUC for NULL representation has
bigger consequences - probably have to related to RAISE statement, and
to proposed functions to_string, to_array.

---
Takahiro Itagaki

Thank You very much, I'do fix it

Pavel

#14Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#13)
1 attachment(s)
Re: patch (for 9.1) string functions

Hello

I am sending a actualised patch

* removed concat_json
* renamed function rvsr to reverse
* functions format, sprintf and concat* are stable now (as to_char for example)

2010/7/9 Pavel Stehule <pavel.stehule@gmail.com>:

hello

2010/7/9 Takahiro Itagaki <itagaki.takahiro@gmail.com>:

2010/7/8 Pavel Stehule <pavel.stehule@gmail.com>:

sorry, attached fixed patch

Make installcheck for contrib/stringfunc is broken.
Please run regression test with --enable-cassert build.
 test stringfunc           ... TRAP:
FailedAssertion("!(!lc_ctype_is_c())", File: "mbutils.c", Line: 715)
 LOG:  server process (PID 15121) was terminated by signal 6: Aborted

it worked on my station :( - Fedora 64bit

can you send a backtrace, please

Regards

Pavel Stehule

Show quoted text

This patch contains several functions.
- format(fmt text, VARIADIC args "any")
- sprintf(fmt text, VARIADIC args "any")
- concat(VARIADIC args "any")
- concat_ws(separator text, VARIADIC args "any")
- concat_json(VARIADIC args "any")
- concat_sql(VARIADIC args "any")
- rvrs(str text)
- left(str text, n int)
- right(str text, n int)

The first one is in the core, and others are in contrib/stringfunc.
But I think almost
all of them should be in the core, because users want to write portable SQLs.
Contrib modules are not always available.  Note that concat() is
supported by Oracle,
MySQL, and DB2. Also left() and right() are supported by MySQL, DB2,
and SQL Server.

Functions that depend on GUC settings should be marked as VOLATILE
instead of IMMUTABLE. I think format(), sprintf(), and all of
concat()s should be
volatile because at least timestamp depends on datestyle parameter.

ok, I'll fix it

concat_ws() and rvrs() should be renamed to non-abbreviated forms.
How about concat_with_sep() and reverse() ?

I used a well known names - concat_ws (MySQL) and rvrs (Oracle rdbms),
I like concat_ws - concat_with_sep is maybe too long. rvrs is too
short, so I'll rename it to reverse - ok?

I think we should avoid concat_json() at the moment because there is another
development project for JSON support. The result type will be JOIN type rather
than text then.

ok

I'm not sure usefulness of concat_sql(). Why don't you just quote all values
with quotes and separate them with comma?

concat_xxx functions are helpers to serialisation. So when when you
would to generate INSERT statements for some export, and you cannot
use a COPY statement, you can do

FOR r IN
 SELECT ....
LOOP
 RETURN NEXT 'INSERT INTO tab(..) VALUES (' || concat_sql(r.a, r.b, r.c, ... )
END LOOP;
RETURN;

you don't need to solve anything and output is well formated SQL. Some
databases dislike quoted numeric values - and quoted nums can be
sonfusing

format() function prints NULL as "NULL", but RAISE statement in PL/pgSQL
does as "<NULL>".

I prefer just NULL.
maybe some GUC variable
stringfunc.null_string = '<NULL>' in future??

We have some choices for NULL representation. For example, empty string,
NULL, <NULL>, or (null) . What will be our choice?   Each of them looks
equally reasonable for me. GUC idea is also good because we need to
mark format() as VOLATILE anyway. We have nothing to lose.

Can ve to solve it other patch? I know to aversion core hackers to new
GUC. Now I propose just "NULL". The GUC for NULL representation has
bigger consequences - probably have to related to RAISE statement, and
to proposed functions to_string, to_array.

---
Takahiro Itagaki

Thank You very much, I'do fix it

Pavel

Attachments:

stringfunc.diffapplication/octet-stream; name=stringfunc.diffDownload
*** ./contrib/stringfunc/expected/stringfunc.out.orig	2010-03-09 13:28:17.082096569 +0100
--- ./contrib/stringfunc/expected/stringfunc.out	2010-07-09 10:27:50.000000000 +0200
***************
*** 0 ****
--- 1,154 ----
+ 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('%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(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+         concat        
+ ----------------------
+  123hellotf03-09-2010
+ (1 row)
+ 
+ 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)
+ 
+ select concat_sql(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+            concat_sql           
+ --------------------------------
+  1,2,3,'hello',t,f,'03-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)
+ 
*** ./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-09 10:23:49.319828876 +0200
***************
*** 0 ****
--- 1,43 ----
+ 
+ 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('%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(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ select concat_ws('#',1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ select concat_sql(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);
*** ./contrib/stringfunc/stringfunc.c.orig	2010-03-05 15:05:30.876738040 +0100
--- ./contrib/stringfunc/stringfunc.c	2010-07-09 10:57:30.203297431 +0200
***************
*** 0 ****
--- 1,905 ----
+ #include "postgres.h"
+ #include "stdio.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_concat_sql(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_concat_sql);
+ 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;
+ }
+ 
+ /*
+  * 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;
+ 			}
+ 
+ 			if (i >= PG_NARGS())
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 						 errmsg("too few parameters specified for printf function")));
+ 						 
+ 			cp = parsePlaceholder(cp, end_ptr, &pdesc, fmt);
+ 			i = setWidthAndPrecision(&pdesc, fcinfo, i);
+ 
+ 			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;
+ 							const char 		*format;
+ 							Oid                     typoutput;
+ 							bool            typIsVarlena;
+ 
+ 							getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 							target_value = OidOutputFunctionCall(typoutput, value);
+ 							
+ 							format = currentFormat(&format_str, &pdesc);
+ 
+ 							/* use wide chars if it is necessary */
+ 							if (pg_database_encoding_max_length() > 1)
+ 							{
+ 								wchar_t *wformat;
+ 								wchar_t	*wbuffer;
+ 								size_t 	fmtlen = (strlen(format) + 1) * sizeof(wchar_t);
+ 								size_t	len = strlen(target_value) + 1;
+ 								
+ 								wformat = palloc(fmtlen);
+ 								char2wchar(wformat, fmtlen, format, strlen(format));
+ 								wbuffer = palloc(len * sizeof(wchar_t));
+ 								
+ 								for (;;)
+ 								{
+ 									int	result;
+ 									
+ 									if ((pdesc.flags & stringfunc_WIDTH) && (pdesc.flags & stringfunc_PRECISION))
+ 										result = swprintf(wbuffer, len, wformat, pdesc.width, 
+ 															 pdesc.precision, target_value);
+ 									else if (pdesc.flags & stringfunc_WIDTH)
+ 										result = swprintf(wbuffer, len, wformat, pdesc.width, target_value);
+ 									else if (pdesc.flags & stringfunc_PRECISION)
+ 										result = swprintf(wbuffer, len, wformat, pdesc.precision, target_value);
+ 									else
+ 										result = swprintf(wbuffer, len, wformat, target_value);
+ 									
+ 									if (result != -1)
+ 									{
+ 										/* append result */
+ 										appendStringInfo(&str, "%ls", wbuffer);
+ 										break;
+ 									}
+ 									else
+ 									{
+ 										/* increase buffer size and repeat */
+ 										len *= 2;
+ 										if ((len * sizeof(pg_wchar)) > MaxAllocSize)
+ 											ereport(ERROR,
+ 												(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ 												 errmsg("out of memory")));
+ 												 
+ 										wbuffer = repalloc(wbuffer, len * sizeof(wchar_t) + 1);
+ 										/* continue */
+ 									}
+ 								}
+ 								
+ 								pfree(wbuffer);
+ 								pfree(wformat);
+ 							}
+ 							else
+ 							{
+ 								/* shortcut for one byte encoding */
+ 								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);
+ 								
+ 								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 to comma separated list. This function
+  * is NULL safe. NULL values are skipped.
+  */
+ Datum
+ stringfunc_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);
+ }
+ 
+ /*
+  * 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);
+ }
+ 
+ /*
+  * Concat string with respect to SQL format. This is NULL safe.
+  * NULLs values are transformated to "NULL" string.
+  */
+ Datum
+ stringfunc_concat_sql(PG_FUNCTION_ARGS)
+ {
+ 	StringInfoData		str;
+ 	text *result;
+ 	int	i;
+ 
+ 	/* 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 (i > 0)
+ 			appendStringInfoChar(&str, ',');
+ 
+ 		if (!PG_ARGISNULL(i))
+ 		{
+ 			Oid	valtype;
+ 			Datum	value;
+ 			Oid                     typoutput;
+ 			bool            typIsVarlena;
+ 			TYPCATEGORY	typcat;
+ 
+ 			/* append n-th value */
+ 			value = PG_GETARG_DATUM(i);
+ 			valtype = get_fn_expr_argtype(fcinfo->flinfo, i);
+ 			typcat = TypeCategory(valtype);
+ 
+ 			if (typcat == 'N' || typcat == 'B')
+ 			{
+ 				getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 				appendStringInfoString(&str, OidOutputFunctionCall(typoutput, value));
+ 			}
+ 			else
+ 			{
+ 				text	*txt;
+ 				text	*quoted_txt;
+ 			
+ 				getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 				
+ 				/* 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
+ 			appendStringInfoString(&str, "NULL");
+ 	}
+ 
+ 	result = cstring_to_text_with_len(str.data, str.len);
+ 	pfree(str.data);
+ 
+ 	PG_RETURN_TEXT_P(result);
+ }
+ 
+ /*
+  * Returns first n chars. When n is negative, then
+  * it returns chars from n+1 position.
+  */
+ Datum
+ stringfunc_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;
+ 		
+ 		len = mb_string_info(str, &sizes, &positions);
+ 		
+ 		if (n > 0)
+ 		{
+ 			n = n > len ? len : n;
+ 			result = cstring_to_text_with_len(p, positions[n - 1] + sizes[n - 1]);
+ 		}
+ 		else
+ 		{
+ 			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
+ stringfunc_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);
+ 		
+ 		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
+ stringfunc_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);
+ 		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);
+ }
+ 
+ /*
+  * Returns length of string, size and position of every
+  * multibyte char in string.
+  */
+ static int
+ mb_string_info(text *str, char **sizes, int **positions)
+ {
+ 	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;
+ 	}
+ 
+ 	return cur_size;
+ }
*** ./contrib/stringfunc/stringfunc.sql.in.orig	2010-07-08 11:27:31.877299385 +0200
--- ./contrib/stringfunc/stringfunc.sql.in	2010-07-09 10:25:24.609297549 +0200
***************
*** 0 ****
--- 1,39 ----
+ 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(VARIADIC args "any")
+ RETURNS text 
+ AS 'MODULE_PATHNAME','stringfunc_concat'
+ 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;
+ 
+ CREATE OR REPLACE FUNCTION concat_sql(VARIADIC args "any")
+ RETURNS text 
+ AS 'MODULE_PATHNAME','stringfunc_concat_sql'
+ LANGUAGE C STABLE;
+ 
+ CREATE OR REPLACE FUNCTION reverse(str text)
+ RETURNS text 
+ AS 'MODULE_PATHNAME','stringfunc_reverse'
+ LANGUAGE C IMMUTABLE STRICT;
+ 
+ CREATE OR REPLACE FUNCTION left(str text, n int)
+ RETURNS text 
+ AS 'MODULE_PATHNAME','stringfunc_left'
+ LANGUAGE C IMMUTABLE STRICT;
+ 
+ CREATE OR REPLACE FUNCTION right(str text, n int)
+ RETURNS text 
+ AS 'MODULE_PATHNAME','stringfunc_right'
+ LANGUAGE C IMMUTABLE STRICT;
*** ./contrib/stringfunc/uninstall_stringfunc.sql.orig	2010-03-05 15:05:47.260738126 +0100
--- ./contrib/stringfunc/uninstall_stringfunc.sql	2010-07-09 10:24:17.314369259 +0200
***************
*** 0 ****
--- 1,8 ----
+ DROP FUNCTION sprintf(fmt text, VARIADIC args "any");
+ DROP FUNCTION sprintf(fmt text);
+ DROP FUNCTION concat(VARIADIC args "any");
+ DROP FUNCTION concat_ws(separator text, VARIADIC args "any");
+ DROP FUNCTION concat_sql(VARIADIC args "any");
+ DROP FUNCTION reverse(str text);
+ DROP FUNCTION left(str text, n int);
+ DROP FUNCTION right(str text, n int);
*** ./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-03-05 14:28:25.807765920 +0100
***************
*** 1262,1267 ****
--- 1262,1270 ----
      <primary>encode</primary>
     </indexterm>
     <indexterm>
+     <primary>format</primary>
+    </indexterm>
+    <indexterm>
      <primary>initcap</primary>
     </indexterm>
     <indexterm>
***************
*** 1450,1455 ****
--- 1453,1473 ----
        </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>
*** ./doc/src/sgml/stringfunc.sgml.orig	2010-03-05 15:11:23.284738364 +0100
--- ./doc/src/sgml/stringfunc.sgml	2010-07-09 10:55:11.569297814 +0200
***************
*** 0 ****
--- 1,73 ----
+ <!-- $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(param1 [, param2 [,...]])</> a concation two or more strings
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>concat_ws(separator, param1 [, param2 [,...]])()</> a concation two 
+       or more strings. First parameter is used as separator.
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>concat_sql(param1 [, param2 [,...]])</> a concation two or more
+       strings. Values are converted to format of SQL constants.
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>reverse(str)</> reverse a string
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>left(str, n)</> returns n chars from start. When n is negative, then 
+       returns chars without last n chars.
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+       <function>right(str, n)</> return last n chars. When n is negative, then 
+       returns chars without first n chars.
+     </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-09 10:58:52.601646871 +0200
***************
*** 3415,3417 ****
--- 3415,3504 ----
  	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
+ 				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);
+ }
*** ./src/include/catalog/pg_proc.h.orig	2010-02-26 03:01:21.000000000 +0100
--- ./src/include/catalog/pg_proc.h	2010-07-09 10:49:42.161422897 +0200
***************
*** 2712,2717 ****
--- 2712,2721 ----
  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 = 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-03-05 13:13:24.979862366 +0100
***************
*** 730,735 ****
--- 730,738 ----
  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);
+ 
  /* 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-09 11:00:05.000000000 +0200
***************
*** 51,53 ****
--- 51,76 ----
  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
*** ./src/test/regress/sql/text.sql.orig	2007-06-07 01:00:50.000000000 +0200
--- ./src/test/regress/sql/text.sql	2010-03-05 14:09:34.083738128 +0100
***************
*** 28,30 ****
--- 28,39 ----
  -- 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);
+ 
#15Erik Rijkers
er@xs4all.nl
In reply to: Pavel Stehule (#14)
1 attachment(s)
Re: patch (for 9.1) string functions

contrib/stringfunc was missing this small change in contrib/Makefile, I think. With it, it
installs and runs make check cleanly.

Erik Rijkers

Attachments:

stringfunc_fix.diffapplication/octet-stream; name=stringfunc_fix.diffDownload
--- contrib/Makefile.orig	2010-07-11 00:06:30.000000000 +0200
+++ contrib/Makefile	2010-07-11 00:07:01.000000000 +0200
@@ -40,6 +40,7 @@
 		pgstattuple	\
 		seg		\
 		spi		\
+		stringfunc	\
 		tablefunc	\
 		test_parser	\
 		tsearch2	\
#16Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Pavel Stehule (#14)
Re: patch (for 9.1) string functions

2010/7/9 Pavel Stehule <pavel.stehule@gmail.com>:

I am sending a actualised patch
* removed concat_json
* renamed function rvsr to reverse
* functions format, sprintf and concat* are stable now (as to_char for example)

I'd like to move all proposed functions into the core, and not to add
contrib/stringfunc.
I think those functions are very useful and worth adding in core.
* concat(), concat_ws(), reverse(), left() and right() are ready to commit.
* format() is almost ready, except consensus of NULL representation.
* sprintf() is also useful, but we cannot use swprintf() in it because
there are many problems in converting to wide chars. We should
develop mbchar-aware version of %s formatter.
* IMHO, concat_sql() has very limited use cases. Boolean and numeric
values are not quoted, but still need product-specific conversions because
some DBs prefer 1/0 instead of true/false.
Also, dblink_build_sql_insert() provides similar functionality. Will
we have both?

it worked on my station :( - Fedora 64bit

Still failed :-( I used UTF8 database with *locale=C* on 64bit Linux.
char2wchar() doesn't seem to work on C locale. We should avoid
using the function and converting mb chars to wide chars.

select sprintf('>>>%10s %10d<<<', 'hello', 10);
! server closed the connection unexpectedly
TRAP: FailedAssertion("!(!lc_ctype_is_c())", File: "mbutils.c", Line: 715)

#0 0x00000038c0c332f5 in raise () from /lib64/libc.so.6
#1 0x00000038c0c34b20 in abort () from /lib64/libc.so.6
#2 0x00000000006e951d in ExceptionalCondition (conditionName=<value
optimized out>, errorType=<value optimized out>, fileName=<value
optimized out>,
lineNumber=<value optimized out>) at assert.c:57
#3 0x00000000006fa8bf in char2wchar (to=0x1daf188 L"", tolen=16,
from=0x1da95b0 "%*s", fromlen=3) at mbutils.c:715
#4 0x00007f8e8c436d37 in stringfunc_sprintf (fcinfo=0x7fff9bdcd4b0)
at stringfunc.c:463

--
Itagaki Takahiro

#17Robert Haas
robertmhaas@gmail.com
In reply to: Itagaki Takahiro (#16)
Re: patch (for 9.1) string functions

On Sun, Jul 11, 2010 at 10:30 PM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

2010/7/9 Pavel Stehule <pavel.stehule@gmail.com>:

I am sending a actualised patch
* removed concat_json
* renamed function rvsr to reverse
* functions format, sprintf and concat* are stable now (as to_char for example)

I'd like to move all proposed functions into the core, and not to add
contrib/stringfunc.
I think those functions are very useful and worth adding in core.
* concat(), concat_ws(), reverse(), left() and right() are ready to commit.
* format() is almost ready, except consensus of NULL representation.
* sprintf() is also useful, but we cannot use swprintf() in it because
 there are many problems in converting to wide chars. We should
 develop mbchar-aware version of %s formatter.
* IMHO, concat_sql() has very limited use cases. Boolean  and numeric
 values are not quoted, but still need product-specific conversions because
 some DBs prefer 1/0 instead of true/false.
 Also, dblink_build_sql_insert() provides similar functionality. Will
we have both?

I'm all in favor of putting such things in core as are supported by
multiple competing products, but is that really true for all of these?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#18Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Robert Haas (#17)
Re: patch (for 9.1) string functions

2010/7/12 Robert Haas <robertmhaas@gmail.com>:

I'm all in favor of putting such things in core as are supported by
multiple competing products, but is that really true for all of these?

- concat() : MySQL, Oracle, DB2
- concat_ws() : MySQL,
- left(), right() : MySQL, SQL Server, DB2
- reverse() : MySQL, SQL Server, Oracle (as utl_raw.reverse)

concat_sql(), format(), and sprintf() will be our unique features.

--
Itagaki Takahiro

#19Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#17)
Re: patch (for 9.1) string functions

Hello

2010/7/12 Robert Haas <robertmhaas@gmail.com>:

On Sun, Jul 11, 2010 at 10:30 PM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

2010/7/9 Pavel Stehule <pavel.stehule@gmail.com>:

I am sending a actualised patch
* removed concat_json
* renamed function rvsr to reverse
* functions format, sprintf and concat* are stable now (as to_char for example)

I'd like to move all proposed functions into the core, and not to add
contrib/stringfunc.
I think those functions are very useful and worth adding in core.
* concat(), concat_ws(), reverse(), left() and right() are ready to commit.
* format() is almost ready, except consensus of NULL representation.

what solution for this moment - be a consistent with RAISE statement ???

* sprintf() is also useful, but we cannot use swprintf() in it because
 there are many problems in converting to wide chars. We should
 develop mbchar-aware version of %s formatter.

ook I'll work on this - but there is same problem with NULL like a
format function

* IMHO, concat_sql() has very limited use cases. Boolean  and numeric
 values are not quoted, but still need product-specific conversions because
 some DBs prefer 1/0 instead of true/false.
 Also, dblink_build_sql_insert() provides similar functionality. Will
we have both?

I can remove it - when I checked it I found so it doesn't well
serialize PostgreSQL specific types as array or record, so I am not
against to remove it now.

I'm all in favor of putting such things in core as are supported by
multiple competing products, but is that really true for all of these?

I have not a strong opinion on this - I would to like see reverse and
format in core. But I think, so contrib is enought. Can somebody from
commiters to decide it, please? Any sprintf implemenation needs lots
of code - minimally for this function I prefer contrib for this
function.

Regards

Pavel Stehule

Show quoted text

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#20Robert Haas
robertmhaas@gmail.com
In reply to: Itagaki Takahiro (#18)
Re: patch (for 9.1) string functions

On Jul 11, 2010, at 10:32 PM, Itagaki Takahiro <itagaki.takahiro@gmail.com> wrote:

2010/7/12 Robert Haas <robertmhaas@gmail.com>:

I'm all in favor of putting such things in core as are supported by
multiple competing products, but is that really true for all of these?

- concat() : MySQL, Oracle, DB2
- concat_ws() : MySQL,
- left(), right() : MySQL, SQL Server, DB2
- reverse() : MySQL, SQL Server, Oracle (as utl_raw.reverse)

concat_sql(), format(), and sprintf() will be our unique features.

I think concat, left, right, and reverse should go into core, and perhaps format also. The rest sound like contrib material to me.

...Robert

#21Kevin Grittner
Kevin.Grittner@wicourts.gov
In reply to: Itagaki Takahiro (#16)
Re: patch (for 9.1) string functions

Itagaki Takahiro <itagaki.takahiro@gmail.com> wrote:

I'd like to move all proposed functions into the core, and not to
add contrib/stringfunc.

Still failed :-( I used UTF8 database with *locale=C* on 64bit
Linux.
char2wchar() doesn't seem to work on C locale. We should avoid
using the function and converting mb chars to wide chars.

select sprintf('>>>%10s %10d<<<', 'hello', 10);
! server closed the connection unexpectedly
TRAP: FailedAssertion("!(!lc_ctype_is_c())", File: "mbutils.c",
Line: 715)

#0 0x00000038c0c332f5 in raise () from /lib64/libc.so.6
#1 0x00000038c0c34b20 in abort () from /lib64/libc.so.6
#2 0x00000000006e951d in ExceptionalCondition
(conditionName=<value optimized out>, errorType=<value optimized
out>, fileName=<value optimized out>, lineNumber=<value optimized
out>) at assert.c:57
#3 0x00000000006fa8bf in char2wchar (to=0x1daf188 L"", tolen=16,
from=0x1da95b0 "%*s", fromlen=3) at mbutils.c:715
#4 0x00007f8e8c436d37 in stringfunc_sprintf
(fcinfo=0x7fff9bdcd4b0)
at stringfunc.c:463

Based on this and subsequent posts, I've changed this patch's status
to "Waiting on Author".

-Kevin

#22Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kevin Grittner (#21)
Re: patch (for 9.1) string functions

2010/7/12 Kevin Grittner <Kevin.Grittner@wicourts.gov>:

Itagaki Takahiro <itagaki.takahiro@gmail.com> wrote:

I'd like to move all proposed functions into the core, and not to
add contrib/stringfunc.

Still failed :-(  I used UTF8 database with *locale=C* on 64bit
Linux.
char2wchar() doesn't seem to work on C locale. We should avoid
using the function and converting mb chars to wide chars.

  select sprintf('>>>%10s %10d<<<', 'hello', 10);
! server closed the connection unexpectedly
TRAP: FailedAssertion("!(!lc_ctype_is_c())", File: "mbutils.c",
Line: 715)

#0  0x00000038c0c332f5 in raise () from /lib64/libc.so.6
#1  0x00000038c0c34b20 in abort () from /lib64/libc.so.6
#2  0x00000000006e951d in ExceptionalCondition
(conditionName=<value optimized out>, errorType=<value optimized
out>, fileName=<value optimized out>, lineNumber=<value optimized
out>) at assert.c:57
#3  0x00000000006fa8bf in char2wchar (to=0x1daf188 L"", tolen=16,
from=0x1da95b0 "%*s", fromlen=3) at mbutils.c:715
#4  0x00007f8e8c436d37 in stringfunc_sprintf
(fcinfo=0x7fff9bdcd4b0)
at stringfunc.c:463

Based on this and subsequent posts, I've changed this patch's status
to "Waiting on Author".

ook - I'll send actualised version tomorrow

Pavel

Show quoted text

-Kevin

#23Pavel Stehule
pavel.stehule@gmail.com
In reply to: Itagaki Takahiro (#18)
1 attachment(s)
Re: patch (for 9.1) string functions

Hello

so this is actualised patch:

* concat_sql removed
* left, right, reverse and concat are in core
* printf and concat_ws are in contrib
* format show "<NULL>" as NULL string
* removed an using of wide chars

todo:

NULL handling for printf function

Query:
what is corect result for

* printf(">>%3.2d<<", NULL) ??
* printf(">>%3.2s", NULL) ??

Regards

Pavel Stehule

2010/7/12 Itagaki Takahiro <itagaki.takahiro@gmail.com>:

Show quoted text

2010/7/12 Robert Haas <robertmhaas@gmail.com>:

I'm all in favor of putting such things in core as are supported by
multiple competing products, but is that really true for all of these?

- concat() : MySQL, Oracle, DB2
- concat_ws() : MySQL,
- left(), right() : MySQL, SQL Server, DB2
- reverse() : MySQL, SQL Server, Oracle (as utl_raw.reverse)

concat_sql(), format(), and sprintf() will be our unique features.

--
Itagaki Takahiro

Attachments:

stringfunc.diffapplication/octet-stream; name=stringfunc.diffDownload
*** ./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);
#24Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Pavel Stehule (#23)
Re: patch (for 9.1) string functions

2010/7/13 Pavel Stehule <pavel.stehule@gmail.com>:

so this is actualised patch:
* concat_sql removed
* left, right, reverse and concat are in core
* printf and concat_ws are in contrib
* format show "<NULL>" as NULL string
* removed an using of wide chars

I think function codes in the core (concat, format, left, right,
and reverse) are ready for committers. They also have docs, but
the names are not listed in Index page (bookindex.html).
Please add
<indexterm>
<primary>funcname</primary>
</indexterm>
in func.sgml for each new function.

However, I have a couple of comments to stringfunc module. sprintf()
and concat_ws() are not installed by default, but provided by the module.

todo:
NULL handling for printf function

I like <NULL> for null arguments. It is just same as format() and RAISE.

=== Questions ===
* concat_ws() transforms NULLs into empty strings.
Is it an intended behavior and compatible with MySQL?
Note that string_agg() doesn't add separators to NULLs.

=# SELECT coalesce(concat_ws(',', 'A', NULL, 'B'), '(null)');
coalesce
----------
A,,B
(1 row)

* concat_ws() returns NULL when the separator is NULL.
Is it an intended behavior and compatible with MySQL?

=# SELECT coalesce(concat_ws(NULL, 'A', NULL, 'B'), '(null)');
coalesce
----------
(null)
(1 row)

=== Trivial issues ===
* Some function prototypes are declared but not used.
We can just remove them.
- mb_string_info()
- stringfunc_concat(PG_FUNCTION_ARGS);
- stringfunc_left(PG_FUNCTION_ARGS);
- stringfunc_right(PG_FUNCTION_ARGS);
- stringfunc_reverse(PG_FUNCTION_ARGS);

* Some error messages need to be improved.
For example, "1th" is wrong.
=# select sprintf('>>>%*s<<<', NULL, 'abcdef');
ERROR: null value not allowed
HINT: width (1th) arguments is NULL

* sprintf() has some typos in error messages
For example, "sprinf".

--
Itagaki Takahiro

#25Pavel Stehule
pavel.stehule@gmail.com
In reply to: Itagaki Takahiro (#24)
1 attachment(s)
Re: patch (for 9.1) string functions

Hello

2010/7/13 Itagaki Takahiro <itagaki.takahiro@gmail.com>:

2010/7/13 Pavel Stehule <pavel.stehule@gmail.com>:

so this is actualised patch:
* concat_sql removed
* left, right, reverse and concat are in core
* printf and concat_ws are in contrib
* format show "<NULL>" as NULL string
* removed an using of wide chars

I think function codes in the core (concat, format, left, right,
and reverse) are ready for committers. They also have docs, but
the names are not listed in Index page (bookindex.html).
Please add
  <indexterm>
   <primary>funcname</primary>
  </indexterm>
in func.sgml for each new function.

fixed

However, I have a couple of comments to stringfunc module. sprintf()
and concat_ws() are not installed by default, but provided by the module.

todo:
NULL handling for printf function

I like <NULL> for null arguments. It is just same as format() and RAISE.

done

=== Questions ===
* concat_ws() transforms NULLs into empty strings.
Is it an intended behavior and compatible with MySQL?
Note that string_agg() doesn't add separators to NULLs.

no I was wrong - original concat_ws just ignore NULL - fixed, now
concat_ws has same behave like original.

 =# SELECT coalesce(concat_ws(',', 'A', NULL, 'B'), '(null)');
  coalesce
 ----------
  A,,B
 (1 row)

* concat_ws() returns NULL when the separator is NULL.
Is it an intended behavior and compatible with MySQL?

 =# SELECT coalesce(concat_ws(NULL, 'A', NULL, 'B'), '(null)');
  coalesce
 ----------
  (null)
 (1 row)

=== Trivial issues ===
* Some function prototypes are declared but not used.
 We can just remove them.
 - mb_string_info()
 - stringfunc_concat(PG_FUNCTION_ARGS);
 - stringfunc_left(PG_FUNCTION_ARGS);
 - stringfunc_right(PG_FUNCTION_ARGS);
 - stringfunc_reverse(PG_FUNCTION_ARGS);

* Some error messages need to be improved.
 For example, "1th" is wrong.
   =# select sprintf('>>>%*s<<<', NULL, 'abcdef');
   ERROR:  null value not allowed
   HINT:  width (1th) arguments is NULL

have you a some idea about it?

* sprintf() has some typos in error messages
 For example, "sprinf".

fixed

--
Itagaki Takahiro

Regards

Pavel

Attachments:

stringfunc.diffapplication/octet-stream; name=stringfunc.diffDownload
*** ./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-13 09:47:40.000000000 +0200
***************
*** 0 ****
--- 1,101 ----
+ 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)
+ 
+ select sprintf('%d', NULL);
+  sprintf 
+ ---------
+  <NULL>
+ (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)
+ 
+ select concat_ws(NULL,10,20,30) is null; -- yes, have to be true
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ select concat_ws(',',10,20,null,30);
+  concat_ws 
+ -----------
+  10,20,30
+ (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-13 09:47:30.658207573 +0200
***************
*** 0 ****
--- 1,31 ----
+ 
+ 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);
+ select sprintf('%d', NULL);
+ 
+ /*
+  * concat tests
+  */
+ select concat_ws('#',1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ select concat_ws(NULL,10,20,30) is null; -- yes, have to be true
+ select concat_ws(',',10,20,null,30);
+ 
*** ./contrib/stringfunc/stringfunc.c.orig	2010-03-05 15:05:30.876738040 +0100
--- ./contrib/stringfunc/stringfunc.c	2010-07-13 09:44:08.001207494 +0200
***************
*** 0 ****
--- 1,579 ----
+ #include "postgres.h"
+ #include "string.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"
+ 
+ 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_ws(PG_FUNCTION_ARGS);
+ 
+ /*
+  * V1 registrations
+  */
+ PG_FUNCTION_INFO_V1(stringfunc_sprintf);
+ PG_FUNCTION_INFO_V1(stringfunc_sprintf_nv);
+ PG_FUNCTION_INFO_V1(stringfunc_concat_ws);
+ 
+ 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 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 sprintf format"),
+ 								 errdetail("missing precision value")));
+ 					}
+ 				}
+ 				else 
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 							 errmsg("broken sprintf 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
+ 				/* append a NULL string */
+ 				append_string(&str, &pdesc, "<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;
+ 	
+ 	/* when separator is NULL, then return NULL */
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	sepstr = text_to_cstring(PG_GETARG_TEXT_P(0));
+ 	initStringInfo(&str);
+ 
+ 	for(i = 1; i < PG_NARGS(); i++)
+ 	{
+ 		if (!PG_ARGISNULL(i))
+ 		{
+ 			Oid	valtype;
+ 			Datum	value;
+ 			Oid                     typoutput;
+ 			bool            typIsVarlena;
+ 
+ 			if (i > 1)
+ 				appendStringInfoString(&str, sepstr);
+ 
+ 			/* 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-13 09:52:22.085208093 +0200
***************
*** 1246,1251 ****
--- 1246,1254 ----
     <indexterm>
      <primary>chr</primary>
     </indexterm>
+     <indexterm>
+     <primary>concat</primary>
+    </indexterm>
     <indexterm>
      <primary>convert</primary>
     </indexterm>
***************
*** 1262,1270 ****
--- 1265,1279 ----
      <primary>encode</primary>
     </indexterm>
     <indexterm>
+     <primary>format</primary>
+    </indexterm>
+    <indexterm>
      <primary>initcap</primary>
     </indexterm>
     <indexterm>
+     <primary>left</primary>
+    </indexterm>
+    <indexterm>
      <primary>lpad</primary>
     </indexterm>
     <indexterm>
***************
*** 1292,1297 ****
--- 1301,1312 ----
      <primary>replace</primary>
     </indexterm>
     <indexterm>
+     <primary>reverse</primary>
+    </indexterm>
+    <indexterm>
+     <primary>right</primary>
+    </indexterm>
+    <indexterm>
      <primary>rpad</primary>
     </indexterm>
     <indexterm>
***************
*** 1372,1377 ****
--- 1387,1405 ----
  
        <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 ****
--- 1478,1498 ----
        </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 ****
--- 1505,1524 ----
        </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 ****
--- 1733,1764 ----
  
        <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);
#26Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#25)
Re: patch (for 9.1) string functions

Hello

I have a one idea nonstandard enhancing of sprintf - relatie often job
is a quoting in PostgreSQL. So sprintf should have a special formats
for quoted values. What do you think about

%lq ... literal quoted
%iq ... ident quoted

??

Regards

Pavel

2010/7/13 Pavel Stehule <pavel.stehule@gmail.com>:

Show quoted text

Hello

2010/7/13 Itagaki Takahiro <itagaki.takahiro@gmail.com>:

2010/7/13 Pavel Stehule <pavel.stehule@gmail.com>:

so this is actualised patch:
* concat_sql removed
* left, right, reverse and concat are in core
* printf and concat_ws are in contrib
* format show "<NULL>" as NULL string
* removed an using of wide chars

I think function codes in the core (concat, format, left, right,
and reverse) are ready for committers. They also have docs, but
the names are not listed in Index page (bookindex.html).
Please add
  <indexterm>
   <primary>funcname</primary>
  </indexterm>
in func.sgml for each new function.

fixed

However, I have a couple of comments to stringfunc module. sprintf()
and concat_ws() are not installed by default, but provided by the module.

todo:
NULL handling for printf function

I like <NULL> for null arguments. It is just same as format() and RAISE.

done

=== Questions ===
* concat_ws() transforms NULLs into empty strings.
Is it an intended behavior and compatible with MySQL?
Note that string_agg() doesn't add separators to NULLs.

no I was  wrong - original concat_ws just ignore NULL - fixed, now
concat_ws has same behave like original.

 =# SELECT coalesce(concat_ws(',', 'A', NULL, 'B'), '(null)');
  coalesce
 ----------
  A,,B
 (1 row)

* concat_ws() returns NULL when the separator is NULL.
Is it an intended behavior and compatible with MySQL?

 =# SELECT coalesce(concat_ws(NULL, 'A', NULL, 'B'), '(null)');
  coalesce
 ----------
  (null)
 (1 row)

=== Trivial issues ===
* Some function prototypes are declared but not used.
 We can just remove them.
 - mb_string_info()
 - stringfunc_concat(PG_FUNCTION_ARGS);
 - stringfunc_left(PG_FUNCTION_ARGS);
 - stringfunc_right(PG_FUNCTION_ARGS);
 - stringfunc_reverse(PG_FUNCTION_ARGS);

* Some error messages need to be improved.
 For example, "1th" is wrong.
   =# select sprintf('>>>%*s<<<', NULL, 'abcdef');
   ERROR:  null value not allowed
   HINT:  width (1th) arguments is NULL

have you a some idea about it?

* sprintf() has some typos in error messages
 For example, "sprinf".

fixed

--
Itagaki Takahiro

Regards

Pavel

#27Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Pavel Stehule (#25)
1 attachment(s)
Re: patch (for 9.1) string functions

I reviewed the core changes of the patch. I don't think we need
mb_string_info() at all. Instead, we can just call pg_mbxxx() functions.

I rewrote the patch to use pg_mbstrlen_with_len() and pg_mbcharcliplen().
What do you think the changes? It requires re-counting lengths of multi-byte
strings in some cases, but the code will be much simpler and can avoid
allocating length buffers.

I'd like to apply contrib/stringinfo apart from the core changes,
because there seems to be still some idea to improve sprintf().

--
Itagaki Takahiro

Attachments:

stringfunc_core-20100721.diffapplication/octet-stream; name=stringfunc_core-20100721.diffDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 70dab53..488e4c4 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 1250,1255 ****
--- 1250,1258 ----
     <indexterm>
      <primary>chr</primary>
     </indexterm>
+     <indexterm>
+     <primary>concat</primary>
+    </indexterm>
     <indexterm>
      <primary>convert</primary>
     </indexterm>
***************
*** 1266,1274 ****
--- 1269,1283 ----
      <primary>encode</primary>
     </indexterm>
     <indexterm>
+     <primary>format</primary>
+    </indexterm>
+    <indexterm>
      <primary>initcap</primary>
     </indexterm>
     <indexterm>
+     <primary>left</primary>
+    </indexterm>
+    <indexterm>
      <primary>lpad</primary>
     </indexterm>
     <indexterm>
***************
*** 1296,1301 ****
--- 1305,1316 ----
      <primary>replace</primary>
     </indexterm>
     <indexterm>
+     <primary>reverse</primary>
+    </indexterm>
+    <indexterm>
+     <primary>right</primary>
+    </indexterm>
+    <indexterm>
      <primary>rpad</primary>
     </indexterm>
     <indexterm>
***************
*** 1376,1381 ****
--- 1391,1409 ----
  
        <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>
***************
*** 1454,1459 ****
--- 1482,1502 ----
        </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>
***************
*** 1466,1471 ****
--- 1509,1528 ----
        </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>
***************
*** 1680,1685 ****
--- 1737,1768 ----
  
        <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>
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index af28c15..e62cf6e 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** string_agg_finalfn(PG_FUNCTION_ARGS)
*** 3415,3417 ****
--- 3415,3635 ----
  	else
  		PG_RETURN_NULL();
  }
+ 
+ /*
+  * Replaces % symbols with the external representation of the arguments.
+  *
+  * Syntax: text_format(format text [, arg "any"] ...) RETURNS text
+  */
+ Datum
+ text_format(PG_FUNCTION_ARGS)
+ {
+ 	text		   *fmt;
+ 	size_t			len;
+ 	StringInfoData	str;
+ 	const char	   *cp,
+ 				   *start_ptr,
+ 				   *end_ptr;
+ 	int				n;
+ 	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);
+ 
+ 	n = 1;
+ 	for (cp = start_ptr; cp <= end_ptr; cp++)
+ 	{
+ 		/*
+ 		 * Occurrences of a single % are replaced by the next parameter's
+ 		 * external representation. Double %'s are converted to one %.
+ 		 */
+ 		if (cp[0] != '%')
+ 		{
+ 			appendStringInfoChar(&str, cp[0]);
+ 		}
+ 		else if (cp < end_ptr && cp[1] == '%')
+ 		{
+ 			appendStringInfoChar(&str, '%');
+ 			cp++;
+ 		}
+ 		else if (n >= PG_NARGS())
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("too few parameters for format function")));
+ 		}
+ 		else
+ 		{
+ 			if (PG_ARGISNULL(n))
+ 			{
+ 				/* A NULL value is shown as '<NULL>'. */
+ 				appendStringInfoString(&str, "<NULL>");
+ 			}
+ 			else
+ 			{
+ 				Datum	value;
+ 				Oid		type;
+ 				Oid		typOutput;
+ 				bool	typIsVarlena;
+ 
+ 				/* append n-th value */
+ 				value = PG_GETARG_DATUM(n);
+ 				type = get_fn_expr_argtype(fcinfo->flinfo, n);
+ 				getTypeOutputInfo(type, &typOutput, &typIsVarlena);
+ 				appendStringInfoString(&str,
+ 					OidOutputFunctionCall(typOutput, value));
+ 			}
+ 			n++;
+ 		}
+ 	}
+ 
+ 	/* check if all arguments are used */
+ 	if (n != 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 version of text_format only to validate format string.
+  */
+ Datum
+ text_format_nv(PG_FUNCTION_ARGS)
+ {
+ 	return text_format(fcinfo);
+ }
+ 
+ /*
+  * Concatenates arguments. 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))
+ 		{
+ 			Datum	value;
+ 			Oid		type;
+ 			Oid		typOutput;
+ 			bool	typIsVarlena;
+ 
+ 			/* append n-th value */
+ 			value = PG_GETARG_DATUM(i);
+ 			type = get_fn_expr_argtype(fcinfo->flinfo, i);
+ 			getTypeOutputInfo(type, &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 first n characters. When n is negative, returns chars without
+  * last |n| characters.
+  */
+ Datum
+ text_left(PG_FUNCTION_ARGS)
+ {
+ 	text	   *str = PG_GETARG_TEXT_PP(0);
+ 	const char *p = VARDATA_ANY(str);
+ 	int			len = VARSIZE_ANY_EXHDR(str);
+ 	int			n = PG_GETARG_INT32(1);
+ 	int			rlen;
+ 
+ 	if (n < 0)
+ 		n = pg_mbstrlen_with_len(p, len) + n;
+ 	rlen = pg_mbcharcliplen(p, len, n);
+ 
+ 	PG_RETURN_TEXT_P(cstring_to_text_with_len(p, rlen));
+ }
+ 
+ /*
+  * Returns last n characters. When n is negative, returns string without
+  * first |n| characters.
+  */
+ Datum
+ text_right(PG_FUNCTION_ARGS)
+ {
+ 	text	   *str = PG_GETARG_TEXT_PP(0);
+ 	const char *p = VARDATA_ANY(str);
+ 	int			len = VARSIZE_ANY_EXHDR(str);
+ 	int			n = PG_GETARG_INT32(1);
+ 	int			off;
+ 
+ 	if (n < 0)
+ 		n = -n;
+ 	else
+ 		n = pg_mbstrlen_with_len(p, len) - n;
+ 	off = pg_mbcharcliplen(p, len, n);
+ 
+ 	PG_RETURN_TEXT_P(cstring_to_text_with_len(p + off, len - off));
+ }
+ 
+ /*
+  * Returns reversed string
+  */
+ Datum
+ text_reverse(PG_FUNCTION_ARGS)
+ {
+ 	text		   *str = PG_GETARG_TEXT_PP(0);
+ 	const char	   *p = VARDATA_ANY(str);
+ 	int				len = VARSIZE_ANY_EXHDR(str);
+ 	const char	   *endp = p + len;
+ 	text		   *result;
+ 	char		   *dst;
+ 
+ 	result = palloc(len + VARHDRSZ);
+ 	dst = (char*) VARDATA(result) + len;
+ 	SET_VARSIZE(result, len + VARHDRSZ);
+ 
+ 	if (pg_database_encoding_max_length() > 1)
+ 	{
+ 		/* multibyte version */
+ 		while (p < endp)
+ 		{
+ 			int		sz;
+ 
+ 			sz = pg_mblen(p);
+ 			dst -= sz;
+ 			memcpy(dst, p, sz);
+ 			p += sz;
+ 		}
+ 	}
+ 	else
+ 	{
+ 		/* single byte version */
+ 		while (p < endp)
+ 			*(--dst) = *p++;
+ 	}
+ 
+ 	PG_RETURN_TEXT_P(result);
+ }
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 6036493..87c891d 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("I/O");
*** 2718,2723 ****
--- 2718,2735 ----
  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");
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 45123fd..5c4cd44 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum string_agg_transfn(PG_FUNCT
*** 731,736 ****
--- 731,743 ----
  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);
  
diff --git a/src/test/regress/expected/text.out b/src/test/regress/expected/text.out
index 08d002f..f561d61 100644
*** a/src/test/regress/expected/text.out
--- b/src/test/regress/expected/text.out
*************** ERROR:  operator does not exist: integer
*** 51,53 ****
--- 51,126 ----
  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 i, left('ahoj', i) from generate_series(-5, 5) t(i) order by i;
+  i  | left 
+ ----+------
+  -5 | 
+  -4 | 
+  -3 | a
+  -2 | ah
+  -1 | aho
+   0 | 
+   1 | a
+   2 | ah
+   3 | aho
+   4 | ahoj
+   5 | ahoj
+ (11 rows)
+ 
+ select i, right('ahoj', i) from generate_series(-5, 5) t(i) order by i;
+  i  | right 
+ ----+-------
+  -5 | 
+  -4 | 
+  -3 | j
+  -2 | oj
+  -1 | hoj
+   0 | 
+   1 | j
+   2 | oj
+   3 | hoj
+   4 | ahoj
+   5 | ahoj
+ (11 rows)
+ 
diff --git a/src/test/regress/sql/text.sql b/src/test/regress/sql/text.sql
index b739e56..e22f18b 100644
*** a/src/test/regress/sql/text.sql
--- b/src/test/regress/sql/text.sql
*************** select 'four: ' || 2+2;
*** 28,30 ****
--- 28,50 ----
  -- 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 i, left('ahoj', i) from generate_series(-5, 5) t(i) order by i;
+ select i, right('ahoj', i) from generate_series(-5, 5) t(i) order by i;
#28Pavel Stehule
pavel.stehule@gmail.com
In reply to: Itagaki Takahiro (#27)
Re: patch (for 9.1) string functions

2010/7/21 Itagaki Takahiro <itagaki.takahiro@gmail.com>:

I reviewed the core changes of the patch. I don't think we need
mb_string_info() at all. Instead, we can just call pg_mbxxx() functions.

I rewrote the patch to use pg_mbstrlen_with_len() and pg_mbcharcliplen().
What do you think the changes? It requires re-counting lengths of multi-byte
strings in some cases, but the code will be much simpler and can avoid
allocating length buffers.

It is a good idea. I see a problem only for "right" function, where
for most common use case a mblen will be called two times. I am not
able to say now, if this can be a performance issue or not. Highly
probably not - only for very large strings.

postgres=# create or replace function randomstr(int) returns text as
$$select string_agg(substring('abcdefghijklmnop' from
trunc(random()*13)::int+1 for 1),'') from generate_series(1,$1) $$
language sql;
CREATE FUNCTION
Time: 27,452 ms

postgres=# select count(*) from(select right(randomstr(1000),3) from
generate_series(1,10000))x;
count
-------
10000
(1 row)

Time: 5615,061 ms
postgres=# select count(*) from(select right(randomstr(1000),3) from
generate_series(1,10000))x;
count
-------
10000
(1 row)

Time: 5606,937 ms
postgres=# select count(*) from(select right(randomstr(1000),3) from
generate_series(1,10000))x;
count
-------
10000
(1 row)

Time: 5630,771 ms

postgres=# select count(*) from(select right(randomstr(1000),3) from
generate_series(1,10000))x;
count
-------
10000
(1 row)

Time: 5753,063 ms
postgres=# select count(*) from(select right(randomstr(1000),3) from
generate_series(1,10000))x;
count
-------
10000
(1 row)
Time: 5755,776 ms

It is about 2% slower for UTF8 encoding. So it isn't significant for me.

I agree with your changes. Thank You very much

Regards

Pavel Stehule

Show quoted text

I'd like to apply contrib/stringinfo apart from the core changes,
because there seems to be still some idea to improve sprintf().

--
Itagaki Takahiro

#29Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Pavel Stehule (#28)
Re: patch (for 9.1) string functions

2010/7/21 Pavel Stehule <pavel.stehule@gmail.com>:

It is about 2% slower for UTF8 encoding. So it isn't significant for me.
I agree with your changes. Thank You very much

Thanks. The core-part is almost ready to commit.
I'll continue to review the contrib part.

But I found there is a design issue in format() :
Appending a '%' is common use-case, but format() cannot append
% char without any spaces between placeholder and the raw % char.

itagaki=# SELECT format('%%%', 10), format('% %%', 10);
format | format
--------+--------
%10 | 10 %
(1 row)

It is a design issue, and RAISE in PL/pgSQL has the same issue, too.
Do we accept the restriction? Or should we add another escape
syntax and/or placeholder pattern?

--
Itagaki Takahiro

#30Pavel Stehule
pavel.stehule@gmail.com
In reply to: Itagaki Takahiro (#29)
Re: patch (for 9.1) string functions

2010/7/23 Itagaki Takahiro <itagaki.takahiro@gmail.com>:

2010/7/21 Pavel Stehule <pavel.stehule@gmail.com>:

It is about 2% slower for UTF8 encoding. So it isn't significant for me.
I agree with your changes. Thank You very much

Thanks. The core-part is almost ready to commit.
I'll continue to review the contrib part.

But I found there is a design issue in format() :
Appending a '%' is common use-case, but format() cannot append
% char without any spaces between placeholder and the raw % char.

itagaki=# SELECT format('%%%', 10), format('% %%', 10);
 format | format
--------+--------
 %10    | 10 %
(1 row)

It is a design issue, and RAISE in PL/pgSQL has the same issue, too.
Do we accept the restriction? Or should we add another escape
syntax and/or placeholder pattern?

I prefer a current behave - RAISE statement uses same and it is not
reported as bug for ten years, what I read a mailing lists. I would to
have a FORMAT implementation simple as possible.

and there is simple workaround:

postgres=# create or replace function fx()
returns void as $$
begin
raise notice '>>>%<<<', '%';
end;
$$ language plpgsql;
CREATE FUNCTION
Time: 560.063 ms
postgres=# select fx();
NOTICE: >>>%<<<
fx
────

(1 row)

Regards

Pavel Stehule

Show quoted text

--
Itagaki Takahiro

#31Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Pavel Stehule (#26)
Re: patch (for 9.1) string functions

I'm reviewing contrib part of the string functions patch.

I found an issue in sprintf() to print integer values. In this case,
'l' (for long type) is used on *all* platforms. For example,
SELECT sprintf('%d', 10);
internally uses
appendStringInfo('%ld', (int64) 10)

But there are some platform that requires to use %lld for int64 format, probably
on Windows. That's why we have INT64_FORMAT macro. sprintf() needs to be
adjusted to use INT64_FORMAT or similar portable codes.

Other portion of the patch seems to be OK for me,
unless you have still some idea to extend the feature.

2010/7/17 Pavel Stehule <pavel.stehule@gmail.com>:

I have a one idea nonstandard enhancing of sprintf - relatie often job
is a quoting in PostgreSQL. So sprintf should have a special formats
for quoted values. What do you think about

%lq ... literal quoted
%iq ... ident quoted

They save some keyboard types to write quote_literal() and quote_ident(), right?
They seem to be useful and reasonable for me. One comment is that you might
want to print NULL values as "NULL" instead of "<NULL>" in such cases.

--
Itagaki Takahiro

#32Pavel Stehule
pavel.stehule@gmail.com
In reply to: Itagaki Takahiro (#31)
Re: patch (for 9.1) string functions

Hello

2010/7/23 Itagaki Takahiro <itagaki.takahiro@gmail.com>:

I'm reviewing contrib part of the string functions patch.

I found an issue in sprintf() to print integer values. In this case,
'l' (for long type) is used on *all* platforms. For example,
 SELECT sprintf('%d', 10);
internally uses
 appendStringInfo('%ld', (int64) 10)

But there are some platform that requires to use %lld for int64 format, probably
on Windows. That's why we have INT64_FORMAT macro. sprintf() needs to be
adjusted to use INT64_FORMAT or similar portable codes.

ok, I'll look on it

Other portion of the patch seems to be OK for me,
unless you have still some idea to extend the feature.

2010/7/17 Pavel Stehule <pavel.stehule@gmail.com>:

I have a one idea nonstandard enhancing of sprintf - relatie often job
is a quoting in PostgreSQL. So sprintf should have a special formats
for quoted values. What do you think about

%lq ... literal quoted
%iq ... ident quoted

They save some keyboard types to write quote_literal() and quote_ident(), right?
They seem to be useful and reasonable for me. One comment is that you might
want to print NULL values as "NULL" instead of "<NULL>" in such cases.

yes, it is good note

Thank You very much

Regards

Pavel Stehule

Show quoted text

--
Itagaki Takahiro

#33Pavel Stehule
pavel.stehule@gmail.com
In reply to: Itagaki Takahiro (#31)
1 attachment(s)
Re: patch (for 9.1) string functions

Hello

2010/7/23 Itagaki Takahiro <itagaki.takahiro@gmail.com>:

I'm reviewing contrib part of the string functions patch.

I found an issue in sprintf() to print integer values. In this case,
'l' (for long type) is used on *all* platforms. For example,
 SELECT sprintf('%d', 10);
internally uses
 appendStringInfo('%ld', (int64) 10)

But there are some platform that requires to use %lld for int64 format, probably
on Windows. That's why we have INT64_FORMAT macro. sprintf() needs to be
adjusted to use INT64_FORMAT or similar portable codes.

fixed - it depends on INT64_FORMAT now.

Other portion of the patch seems to be OK for me,
unless you have still some idea to extend the feature.

2010/7/17 Pavel Stehule <pavel.stehule@gmail.com>:

I have a one idea nonstandard enhancing of sprintf - relatie often job
is a quoting in PostgreSQL. So sprintf should have a special formats
for quoted values. What do you think about

%lq ... literal quoted
%iq ... ident quoted

They save some keyboard types to write quote_literal() and quote_ident(), right?
They seem to be useful and reasonable for me. One comment is that you might
want to print NULL values as "NULL" instead of "<NULL>" in such cases.

NULL is showed as NULL for literal quoting and when ident quoting is
used, then exception is raised.

Maybe last rule is too hard, but it should be a protection before SQL
injection via mal formated SQL

Regards

Pavel

Show quoted text

--
Itagaki Takahiro

Attachments:

stringfunc.difftext/x-patch; charset=US-ASCII; name=stringfunc.diffDownload
*** ./contrib/Makefile.orig	2010-06-14 18:17:56.000000000 +0200
--- ./contrib/Makefile	2010-07-24 08:35:36.983179050 +0200
***************
*** 40,45 ****
--- 40,46 ----
  		pgstattuple	\
  		seg		\
  		spi		\
+ 		stringfunc	\
  		tablefunc	\
  		test_parser	\
  		tsearch2	\
*** ./contrib/stringfunc/expected/stringfunc.out.orig	2010-07-24 08:35:11.716063627 +0200
--- ./contrib/stringfunc/expected/stringfunc.out	2010-07-24 17:09:34.000000000 +0200
***************
*** 0 ****
--- 1,140 ----
+ 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('%.6d', 10);
+  sprintf 
+ ---------
+  000010
+ (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)
+ 
+ select sprintf('%d', NULL);
+  sprintf 
+ ---------
+  <NULL>
+ (1 row)
+ 
+ select sprintf('some identifier: %iq', 'tab');
+        sprintf        
+ ----------------------
+  some identifier: tab
+ (1 row)
+ 
+ select sprintf('some identifier: %iq', 'my tab');
+           sprintf          
+ ---------------------------
+  some identifier: "my tab"
+ (1 row)
+ 
+ select sprintf('some identifier: %iq', NULL); -- should fail
+ ERROR:  null value not allowed
+ HINT:  cannot to use NULL as identifier
+ select sprintf('some literal: %lq', 'yellow dog');
+           sprintf           
+ ----------------------------
+  some literal: 'yellow dog'
+ (1 row)
+ 
+ select sprintf('some literal: %lq', e'yellow \' dog');
+             sprintf            
+ -------------------------------
+  some literal: 'yellow '' dog'
+ (1 row)
+ 
+ select sprintf('some literal: %lq', NULL);
+       sprintf       
+ --------------------
+  some literal: NULL
+ (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)
+ 
+ select concat_ws(NULL,10,20,30) is null; -- yes, have to be true
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ select concat_ws(',',10,20,null,30);
+  concat_ws 
+ -----------
+  10,20,30
+ (1 row)
+ 
*** ./contrib/stringfunc/Makefile.orig	2010-07-24 08:34:20.228063356 +0200
--- ./contrib/stringfunc/Makefile	2010-07-24 08:35:36.984064643 +0200
***************
*** 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-07-24 08:34:20.230063344 +0200
--- ./contrib/stringfunc/sql/stringfunc.sql	2010-07-24 17:09:18.615359594 +0200
***************
*** 0 ****
--- 1,40 ----
+ 
+ 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('%.6d', 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);
+ select sprintf('%d', NULL);
+ 
+ select sprintf('some identifier: %iq', 'tab');
+ select sprintf('some identifier: %iq', 'my tab');
+ select sprintf('some identifier: %iq', NULL); -- should fail
+ 
+ select sprintf('some literal: %lq', 'yellow dog');
+ select sprintf('some literal: %lq', e'yellow \' dog');
+ select sprintf('some literal: %lq', NULL);
+ 
+ /*
+  * concat tests
+  */
+ select concat_ws('#',1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ select concat_ws(NULL,10,20,30) is null; -- yes, have to be true
+ select concat_ws(',',10,20,null,30);
+ 
*** ./contrib/stringfunc/stringfunc.c.orig	2010-07-24 08:34:20.231063477 +0200
--- ./contrib/stringfunc/stringfunc.c	2010-07-24 16:50:53.393361802 +0200
***************
*** 0 ****
--- 1,658 ----
+ #include "postgres.h"
+ #include "string.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"
+ 
+ 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_ws(PG_FUNCTION_ARGS);
+ 
+ /*
+  * V1 registrations
+  */
+ PG_FUNCTION_INFO_V1(stringfunc_sprintf);
+ PG_FUNCTION_INFO_V1(stringfunc_sprintf_nv);
+ PG_FUNCTION_INFO_V1(stringfunc_concat_ws);
+ 
+ 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;
+ 	char		quoting_type;
+ } FormatPlaceholderData;
+ 
+ typedef FormatPlaceholderData *PlaceholderDesc;
+ 
+ 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;
+ 	pdesc->quoting_type = '\0';
+ 
+ 	while (src < end_ptr && pdesc->field_type == '\0')
+ 	{
+ 		c = *++src;
+ 		
+ 		/*
+ 		 * sprintf supports the most common formats that has a sense for high level
+ 		 * programming language with two additions - format tags "iq" and "lq". 
+ 		 * These tags can be used in sense "identifier qouted" and "literal quoted".
+ 		 */
+ 		if (c == 'i' || c == 'l')
+ 		{
+ 			/* look forward - can be a proprietary tag */
+ 			if (src < end_ptr && src[1] == 'q')
+ 			{
+ 				pdesc->field_type = *++src;
+ 				pdesc->quoting_type = c;
+ 
+ 				continue;
+ 			}
+ 		}
+ 
+ 		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++;
+ 					}
+ 					else
+ 					{
+ 						/*
+ 						 * when no one digit is entered, then precision
+ 						 * is zero - digits are optional.
+ 						 */
+ 						CHECK_PAD('.', stringfunc_PRECISION);
+ 						while (src < end_ptr && isdigit(src[1]))
+ 						{
+ 							pdesc->precision = pdesc->precision * 10 + *++src - '0';
+ 						}
+ 					}
+ 				}
+ 				else 
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 							 errmsg("broken sprintf 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, ".*");
+ 
+ 	/* Append l or ll. Decision is based on value of INT64_FORMAT */
+ 	if (pdesc->lenmod == 'l')
+ 	{
+ 		if (strcmp(INT64_FORMAT, "%lld") == 0)
+ 			appendStringInfoString(str, "ll");
+ 		else
+ 			appendStringInfoString(str, "l");
+ 	}
+ 	else 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 = 0;				/* length of substring in bytes */
+ 
+ 	/*
+ 	 * apply precision - it means "show only first n chars", for strings - this flag is 
+ 	 * ignored for proprietary tags %lq and iq, because we can't to show a first n chars 
+ 	 * from possible quoted value. 
+ 	 */
+ 	if (pdesc->flags & stringfunc_PRECISION && pdesc->field_type != 'q')
+ 	{
+ 		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;
+ 
+ 	Oid                     typoutput;
+ 	bool            typIsVarlena;
+ 	Datum 	value;
+ 	Oid valtype;
+ 
+ 	/* 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")));
+ 
+ 			/*
+ 			 * Proprietary tags %iq and %lq needs a call of adequate quoting function.
+ 			 * For tag %lq is special handling of NULL - similar to quote_nullable.
+ 			 * NULL cannot be used as identifier - so exception is raised when tag 
+ 			 * is %iq. 
+ 			 */
+ 			if (pdesc.field_type == 'q')
+ 			{
+ 				if (!PG_ARGISNULL(i))
+ 				{
+ 					char	*target_value;
+ 
+ 					value = PG_GETARG_DATUM(i);
+ 					valtype = get_fn_expr_argtype(fcinfo->flinfo, i);
+ 
+ 					getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 					target_value = OidOutputFunctionCall(typoutput, value);
+ 					
+ 					if (pdesc.quoting_type == 'l')
+ 					{
+ 						text	*txt;
+ 						text	*quoted_txt;
+ 						char	*quoted_str;
+ 						
+ 						txt = cstring_to_text(target_value);
+ 						quoted_txt = DatumGetTextP(DirectFunctionCall1(quote_literal, PointerGetDatum(txt)));
+ 						quoted_str = text_to_cstring(quoted_txt);
+ 						append_string(&str, &pdesc, quoted_str);
+ 						
+ 						pfree(quoted_str);
+ 					}
+ 					else
+ 					{
+ 						const char *quoted_str;
+ 						
+ 						quoted_str = quote_identifier(target_value);
+ 						append_string(&str, &pdesc, (char *) quoted_str);
+ 					}
+ 						
+ 					pfree(target_value);
+ 				}
+ 				else
+ 				{
+ 					if (pdesc.quoting_type == 'i')
+ 						ereport(ERROR,
+ 							    (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ 							     errmsg("null value not allowed"),
+ 							     errhint("cannot to use NULL as identifier")));
+ 							
+ 					append_string(&str, &pdesc, "NULL");
+ 				}
+ 			}
+ 			else if (!PG_ARGISNULL(i))
+ 		        {
+ 				/* 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;
+ 
+ 							getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ 							target_value = OidOutputFunctionCall(typoutput, value);
+ 							
+ 							append_string(&str, &pdesc, target_value);
+ 							pfree(target_value);
+ 						}
+ 						break;
+ 					default:
+ 						elog(ERROR, "unknown format: %c", pdesc.field_type);
+ 				}
+ 			}
+ 			else
+ 				/* append a NULL string */
+ 				append_string(&str, &pdesc, "<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;
+ 	
+ 	/* when separator is NULL, then return NULL */
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	sepstr = text_to_cstring(PG_GETARG_TEXT_P(0));
+ 	initStringInfo(&str);
+ 
+ 	for(i = 1; i < PG_NARGS(); i++)
+ 	{
+ 		if (!PG_ARGISNULL(i))
+ 		{
+ 			Oid	valtype;
+ 			Datum	value;
+ 			Oid                     typoutput;
+ 			bool            typIsVarlena;
+ 
+ 			if (i > 1)
+ 				appendStringInfoString(&str, sepstr);
+ 
+ 			/* 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.orig	2010-07-24 08:34:20.233062905 +0200
--- ./contrib/stringfunc/stringfunc.sql	2010-07-24 09:41:27.224063513 +0200
***************
*** 0 ****
--- 1,14 ----
+ CREATE OR REPLACE FUNCTION sprintf(fmt text, VARIADIC args "any")
+ RETURNS text 
+ AS '$libdir/stringfunc','stringfunc_sprintf'
+ LANGUAGE C STABLE;
+ 
+ CREATE OR REPLACE FUNCTION sprintf(fmt text)
+ RETURNS text 
+ AS '$libdir/stringfunc','stringfunc_sprintf_nv'
+ LANGUAGE C STABLE;
+ 
+ CREATE OR REPLACE FUNCTION concat_ws(separator text, VARIADIC args "any")
+ RETURNS text 
+ AS '$libdir/stringfunc','stringfunc_concat_ws'
+ LANGUAGE C STABLE;
*** ./contrib/stringfunc/uninstall_stringfunc.sql.orig	2010-07-24 08:34:20.234063319 +0200
--- ./contrib/stringfunc/uninstall_stringfunc.sql	2010-07-24 08:35:36.987062529 +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-06-14 19:25:24.000000000 +0200
--- ./doc/src/sgml/contrib.sgml	2010-07-24 08:35:36.987062529 +0200
***************
*** 115,120 ****
--- 115,121 ----
   &seg;
   &contrib-spi;
   &sslinfo;
+  &stringfunc;
   &tablefunc;
   &test-parser;
   &tsearch2;
*** ./doc/src/sgml/filelist.sgml.orig	2010-06-14 19:25:24.000000000 +0200
--- ./doc/src/sgml/filelist.sgml	2010-07-24 08:35:36.989062796 +0200
***************
*** 125,130 ****
--- 125,131 ----
  <!entity pgtrgm          SYSTEM "pgtrgm.sgml">
  <!entity pgupgrade       SYSTEM "pgupgrade.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-07-03 19:21:48.000000000 +0200
--- ./doc/src/sgml/func.sgml	2010-07-24 08:35:37.001070823 +0200
***************
*** 1250,1255 ****
--- 1250,1258 ----
     <indexterm>
      <primary>chr</primary>
     </indexterm>
+     <indexterm>
+     <primary>concat</primary>
+    </indexterm>
     <indexterm>
      <primary>convert</primary>
     </indexterm>
***************
*** 1266,1274 ****
--- 1269,1283 ----
      <primary>encode</primary>
     </indexterm>
     <indexterm>
+     <primary>format</primary>
+    </indexterm>
+    <indexterm>
      <primary>initcap</primary>
     </indexterm>
     <indexterm>
+     <primary>left</primary>
+    </indexterm>
+    <indexterm>
      <primary>lpad</primary>
     </indexterm>
     <indexterm>
***************
*** 1296,1301 ****
--- 1305,1316 ----
      <primary>replace</primary>
     </indexterm>
     <indexterm>
+     <primary>reverse</primary>
+    </indexterm>
+    <indexterm>
+     <primary>right</primary>
+    </indexterm>
+    <indexterm>
      <primary>rpad</primary>
     </indexterm>
     <indexterm>
***************
*** 1376,1381 ****
--- 1391,1409 ----
  
        <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>
***************
*** 1454,1459 ****
--- 1482,1502 ----
        </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>
***************
*** 1466,1471 ****
--- 1509,1528 ----
        </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>
***************
*** 1680,1685 ****
--- 1737,1768 ----
  
        <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-07-24 08:35:11.720062485 +0200
--- ./doc/src/sgml/stringfunc.sgml	2010-07-24 17:05:58.474380848 +0200
***************
*** 0 ****
--- 1,47 ----
+ <!-- $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 - it 
+       simplyfied version of libc sprintf function - it doesn't support length
+       modifiers and it will do necessary conversions automaticaly.  
+     </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-07-24 08:31:50.617103415 +0200
--- ./src/backend/utils/adt/varlena.c	2010-07-24 08:53:52.567065985 +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,3629 ----
  	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 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);
+ 	const char *p = VARDATA_ANY(str);
+ 	int			len = VARSIZE_ANY_EXHDR(str);
+ 	int			n = PG_GETARG_INT32(1);
+ 	int			rlen;
+ 
+ 	if (n < 0)
+ 		n = pg_mbstrlen_with_len(p, len) + n;
+ 	rlen = pg_mbcharcliplen(p, len, n);
+ 
+ 	PG_RETURN_TEXT_P(cstring_to_text_with_len(p, rlen));
+ }
+ 
+ /*
+  * 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);
+ 	const char *p = VARDATA_ANY(str);
+ 	int			len = VARSIZE_ANY_EXHDR(str);
+ 	int			n = PG_GETARG_INT32(1);
+ 	int			off;
+ 
+ 	if (n < 0)
+ 		n = -n;
+ 	else
+ 		n = pg_mbstrlen_with_len(p, len) - n;
+ 	off = pg_mbcharcliplen(p, len, n);
+ 
+ 	PG_RETURN_TEXT_P(cstring_to_text_with_len(p + off, len - off));
+ }
+ 
+ /*
+  * Returns reversed string
+  */
+ Datum
+ text_reverse(PG_FUNCTION_ARGS)
+ {
+ 	text		   *str = PG_GETARG_TEXT_PP(0);
+ 	const char	   *p = VARDATA_ANY(str);
+ 	int				len = VARSIZE_ANY_EXHDR(str);
+ 	const char	   *endp = p + len;
+ 	text		   *result;
+ 	char		   *dst;
+ 
+ 	result = palloc(len + VARHDRSZ);
+ 	dst = (char*) VARDATA(result) + len;
+ 	SET_VARSIZE(result, len + VARHDRSZ);
+ 
+ 	if (pg_database_encoding_max_length() > 1)
+ 	{
+ 		/* multibyte version */
+ 		while (p < endp)
+ 		{
+ 			int		sz;
+ 
+ 			sz = pg_mblen(p);
+ 			dst -= sz;
+ 			memcpy(dst, p, sz);
+ 			p += sz;
+ 		}
+ 	}
+ 	else
+ 	{
+ 		/* single byte version */
+ 		while (p < endp)
+ 			*(--dst) = *p++;
+ 	}
+ 	
+ 	PG_RETURN_TEXT_P(result);
+ }
*** ./src/include/catalog/pg_proc.h.orig	2010-07-16 04:15:54.000000000 +0200
--- ./src/include/catalog/pg_proc.h	2010-07-24 08:35:37.009065744 +0200
***************
*** 2718,2723 ****
--- 2718,2735 ----
  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-07-22 03:22:35.000000000 +0200
--- ./src/include/utils/builtins.h	2010-07-24 08:35:37.010064201 +0200
***************
*** 732,737 ****
--- 732,744 ----
  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	2010-07-24 08:31:50.772205891 +0200
--- ./src/test/regress/expected/text.out	2010-07-24 09:14:05.000000000 +0200
***************
*** 51,53 ****
--- 51,126 ----
  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 i, left('ahoj', i) from generate_series(-5,5) t(i) order by i;
+  i  | left 
+ ----+------
+  -5 | 
+  -4 | 
+  -3 | a
+  -2 | ah
+  -1 | aho
+   0 | 
+   1 | a
+   2 | ah
+   3 | aho
+   4 | ahoj
+   5 | ahoj
+ (11 rows)
+ 
+ select i, right('ahoj', i) from generate_series(-5,5) t(i) order by i;
+  i  | right 
+ ----+-------
+  -5 | 
+  -4 | 
+  -3 | j
+  -2 | oj
+  -1 | hoj
+   0 | 
+   1 | j
+   2 | oj
+   3 | hoj
+   4 | ahoj
+   5 | ahoj
+ (11 rows)
+ 
*** ./src/test/regress/sql/text.sql.orig	2010-07-24 08:31:50.784065013 +0200
--- ./src/test/regress/sql/text.sql	2010-07-24 08:45:31.173064118 +0200
***************
*** 28,30 ****
--- 28,51 ----
  -- 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 i, left('ahoj', i) from generate_series(-5,5) t(i) order by i;
+ select i, right('ahoj', i) from generate_series(-5,5) t(i) order by i;
#34Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Pavel Stehule (#33)
1 attachment(s)
Re: patch (for 9.1) string functions

I merged and enhanced some part of your patch:
- contrib/stringfunc are merged in the core patch
- Old format() is replaced with sprintf(), but the function name is
still format().
- Support %q as alias for %iq.

2010/7/25 Pavel Stehule <pavel.stehule@gmail.com>:

fixed - it depends on INT64_FORMAT now.

I modified the code a bit not to expect 'll' or 'l'.

%lq ... literal quoted
%iq ... ident quoted

I also modified 'q' without specifier, i.e, %q is handled as same as %lq.

But I found there is a design issue in format() :

I prefer a current behave - RAISE statement uses same and it is not
reported as bug for ten years

I think RAISE is badly designed. Using % as a placeholder has a limitation
to format strings. For example, format() cannot work as concat():
SELECT format('%%', 123, 456) => ERROR

So, my proposal is renaming stringfunc//sprintf() to format(),
and moving it into the core. I think sprintf() is superior to format()
in every aspect; '%s%s' works as concat(), and '%s%%' can append
% without blanks.

Then, concat_ws() will be moved into core because contrib/stringfunc
only has the function now. In addition, I'd like to include the function for
the compatibility to MySQL. Also, concat() and concat_ws() can share
the implementation.

Comments?

--
Itagaki Takahiro

Attachments:

stringfunc_core-20100726.diffapplication/octet-stream; name=stringfunc_core-20100726.diffDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 70dab53..a25307f 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 1251,1256 ****
--- 1251,1262 ----
      <primary>chr</primary>
     </indexterm>
     <indexterm>
+     <primary>concat</primary>
+    </indexterm>
+    <indexterm>
+     <primary>concat_ws</primary>
+    </indexterm>
+    <indexterm>
      <primary>convert</primary>
     </indexterm>
     <indexterm>
***************
*** 1266,1274 ****
--- 1272,1286 ----
      <primary>encode</primary>
     </indexterm>
     <indexterm>
+     <primary>format</primary>
+    </indexterm>
+    <indexterm>
      <primary>initcap</primary>
     </indexterm>
     <indexterm>
+     <primary>left</primary>
+    </indexterm>
+    <indexterm>
      <primary>lpad</primary>
     </indexterm>
     <indexterm>
***************
*** 1296,1301 ****
--- 1308,1319 ----
      <primary>replace</primary>
     </indexterm>
     <indexterm>
+     <primary>reverse</primary>
+    </indexterm>
+    <indexterm>
+     <primary>right</primary>
+    </indexterm>
+    <indexterm>
      <primary>rpad</primary>
     </indexterm>
     <indexterm>
***************
*** 1376,1381 ****
--- 1394,1427 ----
  
        <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>
+         Concatenate all argument values. NULL arguments are ignored.
+        </entry>
+        <entry><literal>concat('abcde', 2, NULL, 22)</literal></entry>
+        <entry><literal>abcde222</literal></entry>
+       </row>
+ 
+       <row>
+        <entry>
+         <literal><function>concat_ws</function>(<parameter>sep</parameter> <type>text</type>,
+         <parameter>str</parameter> <type>"any"</type>,
+         [, <parameter>str</parameter> <type>"any"</type> [...] ])</literal>
+        </entry>
+        <entry><type>text</type></entry>
+        <entry>
+         Concatenate all but first argument values. The first parameter is used
+         as a separator. NULL arguments are ignored.
+        </entry>
+        <entry><literal>concat_ws(',', 'abcde', 2, NULL, 22)</literal></entry>
+        <entry><literal>abcde,2,22</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>
***************
*** 1454,1459 ****
--- 1500,1522 ----
        </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>
+         Format argument values into a string like sprintf function in C library.
+         In addition to standard format characters, it supports <literal>%iq</>
+         (quoted identifier) and <literal>%lq</> (quoted literal).
+         Each format specifiler can also have length modifiler.
+         Write <literal>%%</> to emit a literal <literal>%</>.
+        </entry>
+        <entry><literal>format('%d %s xxx: %s', 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>
***************
*** 1466,1471 ****
--- 1529,1548 ----
        </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>
***************
*** 1680,1685 ****
--- 1757,1788 ----
  
        <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>
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index af28c15..c684053 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
***************
*** 21,26 ****
--- 21,27 ----
  #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/builtins.h"
*************** string_agg_finalfn(PG_FUNCTION_ARGS)
*** 3415,3417 ****
--- 3416,4155 ----
  	else
  		PG_RETURN_NULL();
  }
+ 
+ static text *
+ concat_internal(const char *sepstr, int seplen, FunctionCallInfo fcinfo)
+ {
+ 	StringInfoData	str;
+ 	text		   *result;
+ 	int				i;
+ 
+ 	initStringInfo(&str);
+ 
+ 	for (i = (sepstr == NULL ? 0 : 1); i < PG_NARGS(); i++)
+ 	{
+ 		if (!PG_ARGISNULL(i))
+ 		{
+ 			Oid		valtype;
+ 			Datum	value;
+ 			Oid		typOutput;
+ 			bool	typIsVarlena;
+ 
+ 			if (i > 1)
+ 				appendBinaryStringInfo(&str, sepstr, seplen);
+ 
+ 			/* 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);
+ 
+ 	return result;
+ }
+ 
+ /*
+  * Concatenates arguments. NULL arguments are skipped.
+  */
+ Datum
+ text_concat(PG_FUNCTION_ARGS)
+ {
+ 	PG_RETURN_TEXT_P(concat_internal(NULL, 0, fcinfo));
+ }
+ 
+ /*
+  * Concatenates arguments. Each argument is separated with the first argument.
+  * NULL arguments are skipped.
+  */
+ 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), fcinfo));
+ }
+ 
+ /*
+  * Returns first n characters. When n is negative, returns chars without
+  * last |n| characters.
+  */
+ Datum
+ text_left(PG_FUNCTION_ARGS)
+ {
+ 	text	   *str = PG_GETARG_TEXT_PP(0);
+ 	const char *p = VARDATA_ANY(str);
+ 	int			len = VARSIZE_ANY_EXHDR(str);
+ 	int			n = PG_GETARG_INT32(1);
+ 	int			rlen;
+ 
+ 	if (n < 0)
+ 		n = pg_mbstrlen_with_len(p, len) + n;
+ 	rlen = pg_mbcharcliplen(p, len, n);
+ 
+ 	PG_RETURN_TEXT_P(cstring_to_text_with_len(p, rlen));
+ }
+ 
+ /*
+  * Returns last n characters. When n is negative, returns string without
+  * first |n| characters.
+  */
+ Datum
+ text_right(PG_FUNCTION_ARGS)
+ {
+ 	text	   *str = PG_GETARG_TEXT_PP(0);
+ 	const char *p = VARDATA_ANY(str);
+ 	int			len = VARSIZE_ANY_EXHDR(str);
+ 	int			n = PG_GETARG_INT32(1);
+ 	int			off;
+ 
+ 	if (n < 0)
+ 		n = -n;
+ 	else
+ 		n = pg_mbstrlen_with_len(p, len) - n;
+ 	off = pg_mbcharcliplen(p, len, n);
+ 
+ 	PG_RETURN_TEXT_P(cstring_to_text_with_len(p + off, len - off));
+ }
+ 
+ /*
+  * Returns reversed string
+  */
+ Datum
+ text_reverse(PG_FUNCTION_ARGS)
+ {
+ 	text		   *str = PG_GETARG_TEXT_PP(0);
+ 	const char	   *p = VARDATA_ANY(str);
+ 	int				len = VARSIZE_ANY_EXHDR(str);
+ 	const char	   *endp = p + len;
+ 	text		   *result;
+ 	char		   *dst;
+ 
+ 	result = palloc(len + VARHDRSZ);
+ 	dst = (char*) VARDATA(result) + len;
+ 	SET_VARSIZE(result, len + VARHDRSZ);
+ 
+ 	if (pg_database_encoding_max_length() > 1)
+ 	{
+ 		/* multibyte version */
+ 		while (p < endp)
+ 		{
+ 			int		sz;
+ 
+ 			sz = pg_mblen(p);
+ 			dst -= sz;
+ 			memcpy(dst, p, sz);
+ 			p += sz;
+ 		}
+ 	}
+ 	else
+ 	{
+ 		/* single byte version */
+ 		while (p < endp)
+ 			*(--dst) = *p++;
+ 	}
+ 
+ 	PG_RETURN_TEXT_P(result);
+ }
+ 
+ #define TXTFMT_ZERO				(1 << 0)
+ #define TXTFMT_SPACE			(1 << 1)
+ #define TXTFMT_PLUS				(1 << 2)
+ #define TXTFMT_MINUS			(1 << 3)
+ #define TXTFMT_STAR_WIDTH		(1 << 4)
+ #define TXTFMT_SHARP			(1 << 5)
+ #define TXTFMT_WIDTH			(1 << 6)
+ #define TXTFMT_PRECISION		(1 << 7)
+ #define TXTFMT_STAR_PRECISION	(1 << 8)
+ 
+ typedef struct FormatDesc
+ {
+ 	bits32		flags;
+ 	char		field_type;
+ 	char		quoting_type;
+ 	char		lenmod;
+ 	int32		width;
+ 	int32		precision;
+ } FormatDesc;
+ 
+ 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;
+ }
+ 
+ #define CHECK_PAD(symbol, pad_value) \
+ do { \
+ 	if (desc->flags & (pad_value)) \
+ 		ereport(ERROR, \
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE), \
+ 				 errmsg("invalid input for format function"), \
+ 				 errdetail("Format string is '%s'.", TextDatumGetCString(fmt)), \
+ 				 errhint("Symbol '%c' can be used only once.", (symbol)))); \
+ 	desc->flags |= (pad_value); \
+ } while(0);
+ 
+ /*
+  * parse and verify sprintf parameter
+  *
+  *	%[flags][width][.precision]specifier
+  */
+ static const char *
+ parsePlaceholder(const char *src,
+ 				 const char *end_ptr,
+ 				 FormatDesc *desc,
+ 				 text *fmt)
+ {
+ 	char		c;
+ 
+ 	desc->field_type = '\0';
+ 	desc->quoting_type = '\0';
+ 	desc->lenmod = '\0';
+ 	desc->flags = 0;
+ 	desc->width = 0;
+ 	desc->precision = 0;
+ 
+ 	while (src < end_ptr && desc->field_type == '\0')
+ 	{
+ 		c = *++src;
+ 		
+ 		/*
+ 		 * sprintf supports the most common formats that has a sense for high
+ 		 * level programming language with two additions - format tags "iq"
+ 		 * and "lq". These tags can be used in sense "identifier qouted"
+ 		 * and "literal quoted".
+ 		 */
+ 		if (c == 'i' || c == 'l')
+ 		{
+ 			/* look forward - can be a proprietary tag */
+ 			if (src < end_ptr && src[1] == 'q')
+ 			{
+ 				desc->field_type = *++src;
+ 				desc->quoting_type = c;
+ 
+ 				continue;
+ 			}
+ 		}
+ 
+ 		switch (c)
+ 		{
+ 			case '0':
+ 				CHECK_PAD('0', TXTFMT_ZERO);
+ 				break;
+ 			case ' ':
+ 				CHECK_PAD(' ', TXTFMT_SPACE);
+ 				break;
+ 			case '+':
+ 				CHECK_PAD('+', TXTFMT_PLUS);
+ 				break;
+ 			case '-':
+ 				CHECK_PAD('-', TXTFMT_MINUS);
+ 				break;
+ 			case '*':
+ 				CHECK_PAD('*', TXTFMT_STAR_WIDTH);
+ 				break;
+ 			case '#':
+ 				CHECK_PAD('#', TXTFMT_SHARP);
+ 				break;
+ 			case 'o': case 'i': case 'e': case 'E': case 'f': 
+ 			case 'g': case 'd': case 's': case 'x': case 'X': case 'q':
+ 				desc->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', TXTFMT_WIDTH);
+ 				desc->width = c - '0';
+ 				while (src < end_ptr && isdigit(src[1]))
+ 					desc->width = desc->width * 10 + *++src - '0';
+ 				break;
+ 			case '.':
+ 				if (src < end_ptr)
+ 				{
+ 					if (src[1] == '*')
+ 					{
+ 						CHECK_PAD('.', TXTFMT_STAR_PRECISION);
+ 						src++;
+ 					}
+ 					else
+ 					{
+ 						/*
+ 						 * when no one digit is entered, then precision
+ 						 * is zero - digits are optional.
+ 						 */
+ 						CHECK_PAD('.', TXTFMT_PRECISION);
+ 						while (src < end_ptr && isdigit(src[1]))
+ 							desc->precision = desc->precision * 10 +
+ 											  *++src - '0';
+ 					}
+ 				}
+ 				else 
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 							 errmsg("invalid input for format function"),
+ 							 errdetail("missing precision value")));
+ 				break;
+ 
+ 			default:
+ 				ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("unsupported format tag '%c'", c)));
+ 		}
+ 	}
+ 
+ 	if (desc->field_type == '\0')
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("invalid input for format function")));
+ 
+ 	return src;
+ }
+ 
+ #define MAX_FORMAT_SIZE		32
+ 
+ static void
+ currentFormat(char str[], const FormatDesc *desc)
+ {
+ 	size_t		len = 0;
+ 
+ 	str[len++] = '%';
+ 	
+ 	if (desc->flags & TXTFMT_ZERO)
+ 		str[len++] = '0';
+ 
+ 	if (desc->flags & TXTFMT_MINUS)
+ 		str[len++] = '-';
+ 
+ 	if (desc->flags & TXTFMT_PLUS)
+ 		str[len++] = '+';
+ 		
+ 	if (desc->flags & TXTFMT_SPACE)
+ 		str[len++] = ' ';
+ 		
+ 	if (desc->flags & TXTFMT_SHARP)
+ 		str[len++] = '#';
+ 
+ 	if ((desc->flags & TXTFMT_WIDTH) ||
+ 		(desc->flags & TXTFMT_STAR_WIDTH))
+ 		str[len++] = '*';
+ 		
+ 	if ((desc->flags & TXTFMT_PRECISION) ||
+ 		(desc->flags & TXTFMT_STAR_PRECISION))
+ 	{
+ 		memcpy(str + len, ".*", 2);
+ 		len += 2;
+ 	}
+ 
+ 	if (desc->lenmod == 'l')
+ 	{
+ 		size_t	sz = strlen(INT64_FORMAT) - 2;
+ 		memcpy(str + len, INT64_FORMAT + 1, sz);
+ 		len += sz;
+ 	}
+ 	else if (desc->lenmod != '\0')
+ 		str[len++] = desc->lenmod;
+ 
+ 	str[len++] = desc->field_type;
+ 	str[len] = '\0';
+ }
+ 
+ /*
+  * simulate %+width.precion%s format of sformat function 
+  */
+ static void 
+ append_string(StringInfo str, FormatDesc *desc, const char *string)
+ {
+ 	int	nchars = 0;				/* length of substring in chars */
+ 	int	binlen = 0;				/* length of substring in bytes */
+ 
+ 	/*
+ 	 * apply precision - it means "show only first n chars", for strings - this flag is 
+ 	 * ignored for proprietary tags %lq and iq, because we can't to show a first n chars 
+ 	 * from possible quoted value. 
+ 	 */
+ 	if (desc->flags & TXTFMT_PRECISION && desc->field_type != 'q')
+ 	{
+ 		const char *ptr = string;
+ 		int	  len = desc->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 (desc->flags & TXTFMT_WIDTH)
+ 	{
+ 		if (desc->width > nchars)
+ 		{
+ 			/* add neccessary spaces to begin or end */
+ 			if (desc->flags & TXTFMT_MINUS)
+ 			{
+ 				/* allign to left */
+ 				appendBinaryStringInfo(str, string, binlen);
+ 				appendStringInfoSpaces(str, desc->width - nchars);
+ 			}
+ 			else
+ 			{
+ 				/* allign to right */
+ 				appendStringInfoSpaces(str, desc->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(FormatDesc *desc,
+ 					 FunctionCallInfoData *fcinfo,
+ 					 int current)
+ {
+ 
+ 	/* 
+ 	 * don't allow ambiguous definition
+ 	 */
+ 	if ((desc->flags & TXTFMT_WIDTH) && (desc->flags & TXTFMT_STAR_WIDTH))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("invalid input for format function"),
+ 				 errdetail("ambiguous width definition")));
+ 
+ 	if ((desc->flags & TXTFMT_PRECISION) && (desc->flags & TXTFMT_STAR_PRECISION))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("invalid input for format function"),
+ 				 errdetail("ambiguous precision definition")));
+ 	if (desc->flags & TXTFMT_STAR_WIDTH)
+ 	{
+ 		if (current >= PG_NARGS())
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("too few parameters for format 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)));
+ 		
+ 		desc->width = DatumGetInt32(castValueTo(PG_GETARG_DATUM(current), INT4OID, 
+ 									get_fn_expr_argtype(fcinfo->flinfo, current)));
+ 		/* reset flag */
+ 		desc->flags ^= TXTFMT_STAR_WIDTH;
+ 		desc->flags |= TXTFMT_WIDTH;
+ 		current += 1;
+ 	}
+ 	
+ 	if (desc->flags & TXTFMT_STAR_PRECISION)
+ 	{
+ 		if (current >= PG_NARGS())
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("too few parameters for format 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)));
+ 		
+ 		desc->precision = DatumGetInt32(castValueTo(PG_GETARG_DATUM(current), INT4OID, 
+ 									get_fn_expr_argtype(fcinfo->flinfo, current)));
+ 		/* reset flags */
+ 		desc->flags ^= TXTFMT_STAR_PRECISION;
+ 		desc->flags |= TXTFMT_PRECISION;
+ 		current += 1;
+ 	}
+ 	
+ 	return current;
+ }
+ 
+ /*
+  * Replaces % symbols with the external representation of the arguments.
+  *
+  * Syntax: text_format(format text [, arg "any"] ...) RETURNS text
+  */
+ Datum
+ text_format(PG_FUNCTION_ARGS)
+ {
+ 	text		   *fmt;
+ 	StringInfoData	str;
+ 	int				arg;
+ 	const char	   *start_ptr,
+ 				   *end_ptr,
+ 				   *cp;
+ 	text		   *result;
+ 
+ 	/* returns null when format string is null */
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	fmt = PG_GETARG_TEXT_PP(0);
+ 	start_ptr = VARDATA_ANY(fmt);
+ 	end_ptr = start_ptr + VARSIZE_ANY_EXHDR(fmt) - 1;
+ 
+ 	initStringInfo(&str);
+ 
+ 	arg = 1;
+ 	for (cp = start_ptr; cp <= end_ptr; cp++)
+ 	{
+ 		Oid			typOutput;
+ 		bool		typIsVarlena;
+ 		Datum		value;
+ 		Oid			valtype;
+ 		FormatDesc	desc;
+ 
+ 		if (cp[0] != '%')
+ 		{
+ 			appendStringInfoChar(&str, cp[0]);
+ 			continue;
+ 		}
+ 
+ 		if (cp < end_ptr && cp[1] == '%')
+ 		{
+ 			/* when cp is not pointer on last char, check %% */
+ 			appendStringInfoChar(&str, cp[1]);
+ 			cp++;
+ 			continue;
+ 		}
+ 
+ 		cp = parsePlaceholder(cp, end_ptr, &desc, fmt);
+ 		arg = setWidthAndPrecision(&desc, fcinfo, arg);
+ 
+ 		if (arg >= PG_NARGS())
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("too few parameters for format function")));
+ 
+ 		/*
+ 		 * NULL value is printed as <NULL> in normal cases, but NULL for tag
+ 		 * %lq case - similar to quote_nullable. NULL cannot be used as
+ 		 * identifier - so exception is raised when tag is %iq.
+ 		 */
+ 		if (PG_ARGISNULL(arg))
+ 		{
+ 			if (desc.field_type != 'q')
+ 				append_string(&str, &desc, "<NULL>");
+ 			else if (desc.quoting_type == 'i')
+ 				ereport(ERROR,
+ 					    (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ 					     errmsg("null value not allowed"),
+ 					     errhint("cannot to use NULL as identifier")));
+ 			else						
+ 				append_string(&str, &desc, "NULL");
+ 			arg++;
+ 			continue;
+ 		}
+ 
+ 		/* append n-th value */
+ 		value = PG_GETARG_DATUM(arg);
+ 		valtype = get_fn_expr_argtype(fcinfo->flinfo, arg);
+ 
+ 		/* convert value to target type */
+ 		switch (desc.field_type)
+ 		{
+ 			case 'o': case 'd': case 'i': case 'x': case 'X':
+ 				{
+ 					int64	target_value;
+ 					char	format[MAX_FORMAT_SIZE];
+ 
+ 					desc.lenmod = 'l';
+ 					target_value = DatumGetInt64(castValueTo(value,
+ 													INT8OID, valtype));
+ 					currentFormat(format, &desc);
+ 
+ 					if ((desc.flags & TXTFMT_WIDTH) &&
+ 						(desc.flags & TXTFMT_PRECISION))
+ 						appendStringInfo(&str, format, desc.width,
+ 										 desc.precision, target_value);
+ 					else if (desc.flags & TXTFMT_WIDTH)
+ 						appendStringInfo(&str, format, desc.width,
+ 										 target_value);
+ 					else if (desc.flags & TXTFMT_PRECISION)
+ 						appendStringInfo(&str, format, desc.precision,
+ 										 target_value);
+ 					else
+ 						appendStringInfo(&str, format, target_value);
+ 				}
+ 				break;
+ 			case 'e': case 'f': case 'g': case 'G': case 'E':
+ 				{
+ 					float8	target_value;
+ 					char	format[MAX_FORMAT_SIZE];
+ 					
+ 					target_value = DatumGetFloat8(castValueTo(value,
+ 														FLOAT8OID, valtype));
+ 					currentFormat(format, &desc);
+ 					
+ 					if ((desc.flags & TXTFMT_WIDTH) &&
+ 						(desc.flags & TXTFMT_PRECISION))
+ 						appendStringInfo(&str, format, desc.width,
+ 										 desc.precision, target_value);
+ 					else if (desc.flags & TXTFMT_WIDTH)
+ 						appendStringInfo(&str, format, desc.width,
+ 										 target_value);
+ 					else if (desc.flags & TXTFMT_PRECISION)
+ 						appendStringInfo(&str, format, desc.precision,
+ 										 target_value);
+ 					else
+ 						appendStringInfo(&str, format, target_value);
+ 				}
+ 				break;
+ 			case 's':
+ 				{
+ 					char		*target_value;
+ 
+ 					getTypeOutputInfo(valtype, &typOutput, &typIsVarlena);
+ 					target_value = OidOutputFunctionCall(typOutput, value);
+ 					append_string(&str, &desc, target_value);
+ 					pfree(target_value);
+ 				}
+ 				break;
+ 			case 'q':
+ 				{
+ 					char		*target_value;
+ 
+ 					getTypeOutputInfo(valtype, &typOutput, &typIsVarlena);
+ 					target_value = OidOutputFunctionCall(typOutput, value);
+ 			
+ 					if (desc.quoting_type == 'i')
+ 					{
+ 						const char *quoted_str;
+ 						
+ 						quoted_str = quote_identifier(target_value);
+ 						append_string(&str, &desc, quoted_str);
+ 					}
+ 					else
+ 					{
+ 						text	*txt;
+ 						text	*quoted_txt;
+ 						char	*quoted_str;
+ 
+ 						txt = cstring_to_text(target_value);
+ 						quoted_txt = DatumGetTextP(DirectFunctionCall1(quote_literal, PointerGetDatum(txt)));
+ 						quoted_str = text_to_cstring(quoted_txt);
+ 						append_string(&str, &desc, quoted_str);
+ 
+ 						pfree(quoted_str);
+ 					}
+ 					pfree(target_value);
+ 				}
+ 				break;
+ 			default:
+ 				elog(ERROR, "unknown format: %c", desc.field_type);
+ 		}
+ 		arg++;
+ 	}
+ 
+ 	/* check if all arguments are used */
+ 	if (arg != 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 version of text_format only to validate format string.
+  */
+ Datum
+ text_format_nv(PG_FUNCTION_ARGS)
+ {
+ 	return text_format(fcinfo);
+ }
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 6036493..3547cee 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("I/O");
*** 2718,2723 ****
--- 2718,2737 ----
  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 = 3037 ( 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("concatenate values to text");
+ DATA(insert OID = 3038 ( concat_ws	PGNSP PGUID 12 1 0 2276 f f f f f s 2 0 25 "25 2276" "{25,2276}" "{i,v}" _null_ _null_  text_concat_ws _null_ _null_ _null_ ));
+ DESCR("concatenate values to text with separators");
+ DATA(insert OID = 3039 ( 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("return the first n characters");
+ DATA(insert OID = 3040 ( 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("return the last n characters");
+ DATA(insert OID = 3041 ( 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 = 3042 ( 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 = 3043 ( 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");
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 0c92334..c45faf1 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum string_agg_transfn(PG_FUNCT
*** 732,737 ****
--- 732,745 ----
  extern Datum string_agg_delim_transfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_finalfn(PG_FUNCTION_ARGS);
  
+ extern Datum text_concat(PG_FUNCTION_ARGS);
+ extern Datum text_concat_ws(PG_FUNCTION_ARGS);
+ 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);
  
diff --git a/src/test/regress/expected/text.out b/src/test/regress/expected/text.out
index 08d002f..faf9f91 100644
*** a/src/test/regress/expected/text.out
--- b/src/test/regress/expected/text.out
*************** ERROR:  operator does not exist: integer
*** 51,53 ****
--- 51,255 ----
  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.
+ -- format
+ select format('Hello %s %s', 'World', 10);
+      format     
+ ----------------
+  Hello World 10
+ (1 row)
+ 
+ select format('%%%s%%, date: %s', 10, to_date('20080809','YYYYMMDD'));
+          format         
+ ------------------------
+  %10%, date: 08-09-2008
+ (1 row)
+ 
+ select format('Hello');
+  format 
+ --------
+  Hello
+ (1 row)
+ 
+ select format('Hello %s'); -- error
+ ERROR:  too few parameters for format function
+ select format('Hello',10); -- error
+ ERROR:  too many parameters for format function
+ select format('>>>%10s %10d<<<', 'hello', 10);
+            format            
+ -----------------------------
+  >>>     hello         10<<<
+ (1 row)
+ 
+ select format('>>>%-10s<<<', 'hello');
+       format      
+ ------------------
+  >>>hello     <<<
+ (1 row)
+ 
+ select format('>>>%5.2<<<', 'abcde'); -- error
+ ERROR:  unsupported format tag '<'
+ select format('>>>%*s<<<', 10, 'abcdef');
+       format      
+ ------------------
+  >>>    abcdef<<<
+ (1 row)
+ 
+ select format('>>>%*s<<<', 10); -- error
+ ERROR:  too few parameters for format function
+ select format('%010d', 10);
+    format   
+ ------------
+  0000000010
+ (1 row)
+ 
+ select format('%.6d', 10);
+  format 
+ --------
+  000010
+ (1 row)
+ 
+ select format('%'); -- error
+ ERROR:  invalid input for format function
+ select format('%d', 100.0/3.0);
+  format 
+ --------
+  33
+ (1 row)
+ 
+ select format('%e', 100.0/3.0);
+     format    
+ --------------
+  3.333333e+01
+ (1 row)
+ 
+ select format('%f', 100.0/3.0);
+   format   
+ -----------
+  33.333333
+ (1 row)
+ 
+ select format('%g', 100.0/3.0);
+  format  
+ ---------
+  33.3333
+ (1 row)
+ 
+ select format('%7.4e', 100.0/3.0);
+    format   
+ ------------
+  3.3333e+01
+ (1 row)
+ 
+ select format('%7.4f', 100.0/3.0);
+  format  
+ ---------
+  33.3333
+ (1 row)
+ 
+ select format('%7.4g', 100.0/3.0);
+  format  
+ ---------
+    33.33
+ (1 row)
+ 
+ select format('%d', NULL);
+  format 
+ --------
+  <NULL>
+ (1 row)
+ 
+ select format('some quotes: %q %lq %iq', 'tab', 'tab', 'tab');
+             format            
+ ------------------------------
+  some quotes: 'tab' 'tab' tab
+ (1 row)
+ 
+ select format('some quotes: %q %lq %iq', 'a''"b', 'a''"b', 'a''"b');
+                 format                
+ --------------------------------------
+  some quotes: 'a''"b' 'a''"b' "a'""b"
+ (1 row)
+ 
+ select format('null literal: %lq', NULL);
+        format       
+ --------------------
+  null literal: NULL
+ (1 row)
+ 
+ select format('null identifier: %iq', NULL); -- error
+ ERROR:  null value not allowed
+ HINT:  cannot to use NULL as identifier
+ /*
+  * concat tests
+  */
+ select concat(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+         concat        
+ ----------------------
+  123hellotf03-09-2010
+ (1 row)
+ 
+ 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)
+ 
+ select concat_ws(',',10,20,null,30);
+  concat_ws 
+ -----------
+  10,20,30
+ (1 row)
+ 
+ select concat_ws('',10,20,null,30);
+  concat_ws 
+ -----------
+  102030
+ (1 row)
+ 
+ select concat_ws(NULL,10,20,null,30) is null; -- yes, have to be true
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ /*
+  * others tests
+  */
+ select reverse('abcde');
+  reverse 
+ ---------
+  edcba
+ (1 row)
+ 
+ select i, left('ahoj', i) from generate_series(-5, 5) t(i) order by i;
+  i  | left 
+ ----+------
+  -5 | 
+  -4 | 
+  -3 | a
+  -2 | ah
+  -1 | aho
+   0 | 
+   1 | a
+   2 | ah
+   3 | aho
+   4 | ahoj
+   5 | ahoj
+ (11 rows)
+ 
+ select i, right('ahoj', i) from generate_series(-5, 5) t(i) order by i;
+  i  | right 
+ ----+-------
+  -5 | 
+  -4 | 
+  -3 | j
+  -2 | oj
+  -1 | hoj
+   0 | 
+   1 | j
+   2 | oj
+   3 | hoj
+   4 | ahoj
+   5 | ahoj
+ (11 rows)
+ 
diff --git a/src/test/regress/sql/text.sql b/src/test/regress/sql/text.sql
index b739e56..90d4e67 100644
*** a/src/test/regress/sql/text.sql
--- b/src/test/regress/sql/text.sql
*************** select 'four: ' || 2+2;
*** 28,30 ****
--- 28,75 ----
  -- but not this:
  
  select 3 || 4.0;
+ 
+ -- format
+ select format('Hello %s %s', 'World', 10);
+ select format('%%%s%%, date: %s', 10, to_date('20080809','YYYYMMDD'));
+ select format('Hello');
+ select format('Hello %s'); -- error
+ select format('Hello',10); -- error
+ select format('>>>%10s %10d<<<', 'hello', 10);
+ select format('>>>%-10s<<<', 'hello');
+ select format('>>>%5.2<<<', 'abcde'); -- error
+ select format('>>>%*s<<<', 10, 'abcdef');
+ select format('>>>%*s<<<', 10); -- error
+ select format('%010d', 10);
+ select format('%.6d', 10);
+ select format('%'); -- error
+ 
+ select format('%d', 100.0/3.0);
+ select format('%e', 100.0/3.0);
+ select format('%f', 100.0/3.0);
+ select format('%g', 100.0/3.0);
+ select format('%7.4e', 100.0/3.0);
+ select format('%7.4f', 100.0/3.0);
+ select format('%7.4g', 100.0/3.0);
+ select format('%d', NULL);
+ 
+ select format('some quotes: %q %lq %iq', 'tab', 'tab', 'tab');
+ select format('some quotes: %q %lq %iq', 'a''"b', 'a''"b', 'a''"b');
+ select format('null literal: %lq', NULL);
+ select format('null identifier: %iq', NULL); -- error
+ 
+ /*
+  * concat tests
+  */
+ select concat(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ select concat_ws('#',1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ select concat_ws(',',10,20,null,30);
+ select concat_ws('',10,20,null,30);
+ select concat_ws(NULL,10,20,null,30) is null; -- yes, have to be true
+ 
+ /*
+  * others tests
+  */
+ select reverse('abcde');
+ select i, left('ahoj', i) from generate_series(-5, 5) t(i) order by i;
+ select i, right('ahoj', i) from generate_series(-5, 5) t(i) order by i;
#35Pavel Stehule
pavel.stehule@gmail.com
In reply to: Itagaki Takahiro (#34)
Re: patch (for 9.1) string functions

2010/7/26 Itagaki Takahiro <itagaki.takahiro@gmail.com>:

I merged and enhanced some part of your patch:
 - contrib/stringfunc are merged in the core patch
 - Old format() is replaced with sprintf(), but the function name is
still format().
 - Support %q as alias for %iq.

2010/7/25 Pavel Stehule <pavel.stehule@gmail.com>:

fixed - it depends on INT64_FORMAT now.

I modified the code a bit not to expect 'll' or 'l'.

%lq ... literal quoted
%iq ... ident quoted

I also modified 'q' without specifier, i.e, %q is handled as same as %lq.

But I found there is a design issue in format() :

I prefer a current behave - RAISE statement uses same and it is not
reported as bug for ten years

I think RAISE is badly designed. Using % as a placeholder has a limitation
to format strings. For example, format() cannot work as concat():
 SELECT format('%%', 123, 456) => ERROR

So, my proposal is renaming stringfunc//sprintf() to format(),
and moving it into the core. I think sprintf() is superior to format()
in every aspect; '%s%s' works as concat(), and '%s%%' can append
% without blanks.

Sorry, I am strong against. Using a format just for string string
concation is bad idea - there are a concat function, there are ||
operator. Look on complexity of format/RAISE and sprintf. More - it
can be strange, when we have a "format" function and it is almost
"sprintf". I still prefer simplicity of format - you have a true - it
has a issue, but there are simply workaround

format('%', 123||345)

format is very simple - but usually you don't need to specify a with,
a precision.

sprintf has some issue based on common sprintf implementation and
expecting too. For example a precision is used very dynamically - it
has a different sense for integers and for floats, so I wouldn't have
a sprintf in core.

Then, concat_ws() will be moved into core because contrib/stringfunc
only has the function now. In addition, I'd like to include the function for
the compatibility to MySQL. Also, concat() and concat_ws() can share
the implementation.

Comments?

I disagree - please, return to prev variant

Regards

Pavel Stehule

Show quoted text

--
Itagaki Takahiro

#36Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Pavel Stehule (#35)
Re: patch (for 9.1) string functions

2010/7/26 Pavel Stehule <pavel.stehule@gmail.com>:

sprintf has some issue based on common sprintf implementation and
expecting too. For example a precision is used very dynamically - it
has a different sense for integers and for floats, so I wouldn't have
a sprintf in core.

Why do we need to have similar functions in core and contrib?
It will just confuse users. If you want to RAISE-version of format(),
I don't want to have stringfunc in contrib.

sprintf() is cool! So, I'd like to use sprintf() by default rather than
format() which has limited features. Almost all users don't know
well about contrib modules. Books about functions in inter-databases
don't consider about postgres' contrib modules. That's why I want to
move the useful features into core rather than contrib modules.

--
Itagaki Takahiro

#37Pavel Stehule
pavel.stehule@gmail.com
In reply to: Itagaki Takahiro (#36)
Re: patch (for 9.1) string functions

2010/7/26 Itagaki Takahiro <itagaki.takahiro@gmail.com>:

2010/7/26 Pavel Stehule <pavel.stehule@gmail.com>:

sprintf has some issue based on common sprintf implementation and
expecting too. For example a precision is used very dynamically - it
has a different sense for integers and for floats, so I wouldn't have
a sprintf in core.

Why do we need to have similar functions in core and contrib?
It will just confuse users. If you want to RAISE-version of format(),
I don't want to have stringfunc in contrib.

:(

please, look back to discus about this module. There was desided, so
"format" will be in core and "sprintf" in contrib. One reason for this
decision was complexity of printf's implementation.

sprintf() is cool! So, I'd like to use sprintf() by default rather than
format() which has limited features. Almost all users don't know
well about contrib modules. Books about functions in inter-databases
don't consider about postgres' contrib modules. That's why I want to
move the useful features into core rather than contrib modules.

I have a different opinion and I am not alone. sprintf is good for c
language, but it is problematic in scripting environments, where are
not pointers, where we have more info about variables - where we can
use a introspection - it is like dinosaurus in IT. My implementation
is little bit simple, bacause it is use a buildin functionality - but
still it has more then hundred rows. The full implementation has about
thousand rows. More sprintf is little bit slower than format - it have
to do little bit more work - and it can be confusing for people who
doesn't known it well.

for example - sprintf("%d", 10.2) ---> 10.

next - sprintf respect common standard - but this standard doesn't
calculate with PostgreSQL datatypes - there are not support for
"date", "timestemp" for example.

Function format is designed to work with builtin function to_char.
This is simple and full functional combination - I have not a plan to
replace it.

Regards

Pavel Stehule

Show quoted text

--
Itagaki Takahiro

#38Robert Haas
robertmhaas@gmail.com
In reply to: Itagaki Takahiro (#34)
Re: patch (for 9.1) string functions

On Sun, Jul 25, 2010 at 11:29 PM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

I think RAISE is badly designed. Using % as a placeholder has a limitation
to format strings. For example, format() cannot work as concat():
 SELECT format('%%', 123, 456) => ERROR

It's hard to argue with this, as far as it goes.

So, my proposal is renaming stringfunc//sprintf() to format(),
and moving it into the core. I think sprintf() is superior to format()
in every aspect; '%s%s' works as concat(), and '%s%%' can append
% without blanks.

So forget about format() and put sprintf() in contrib/stringfunc.
That's not an argument for putting anything in core. Perhaps such an
argument can be made, but this isn't it.

Then, concat_ws() will be moved into core because contrib/stringfunc
only has the function now. In addition, I'd like to include the function for
the compatibility to MySQL. Also, concat() and concat_ws() can share
the implementation.

Regardless of where this function ends up, the concat_ws documentation
should contain some mention of the fact that "ws" is intended to mean
"with separator", and that the naming is chosen for compatibility with
MySQL. As for where to put it, I see no terribly compelling reason
why it needs to be in core. You can write array_to_string(array[txt1,
txt2, txt3], sep) and get the same effect as concat_ws(sep, txt1,
txt2, txt3). I don't really want to start maintaining duplicate
functionality for things like this, especially since MySQL users will
no doubt expect that our implementation will be bug-compatible. If
the output isn't identical to what MySQL does for every set of
arguments, it'll be reported as a bug.

Come to think of it, have we checked that the behavior of LEFT, RIGHT,
REVERSE, etc. is the same on other DBs, especially as far as nulls,
empty strings, too-large or negative subscripts, etc is concerned? Is
CONCAT('foo', NULL) => 'foo' really the behavior that everyone else
implements here? And why does CONCAT() take a variadic "ANY"
argument? Shouldn't that be variadic TEXT?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#39Merlin Moncure
mmoncure@gmail.com
In reply to: Robert Haas (#38)
Re: patch (for 9.1) string functions

On Mon, Jul 26, 2010 at 8:02 AM, Robert Haas <robertmhaas@gmail.com> wrote:

Regardless of where this function ends up, the concat_ws documentation
should contain some mention of the fact that "ws" is intended to mean
"with separator",

big +1 on that -- I've been loosely following the thread and I had
assumed 'ws' meant 'wide string' all this time :-).

Come to think of it, have we checked that the behavior of LEFT, RIGHT,
REVERSE, etc. is the same on other DBs, especially as far as nulls,
empty strings, too-large or negative subscripts, etc is concerned?

Probably 'standard' behavior wrt null would be to be strict; return
null if any argument is null. The proposed behavior seems ok though.

CONCAT('foo', NULL) => 'foo' really the behavior that everyone else
implements here?  And why does CONCAT() take a variadic "ANY"
argument?  Shouldn't that be variadic TEXT?

What does that accomplish, besides forcing you to sprinkle every
concat call with text casts (maybe that's not a bad thing?)?

merlin

#40Pavel Stehule
pavel.stehule@gmail.com
In reply to: Merlin Moncure (#39)
Re: patch (for 9.1) string functions

CONCAT('foo', NULL) => 'foo' really the behavior that everyone else
implements here?  And why does CONCAT() take a variadic "ANY"
argument?  Shouldn't that be variadic TEXT?

CONCAT with variadic text parameter will be limited with existing
default casts to text - for example, you can't to cast date to text,
int to text.

postgres=# create or replace function concat(variadic text[]) returns
text as $$select string_agg(x,'') from unnest($1) x$$ language sql;
CREATE FUNCTION

postgres=# select concat('a','b');
concat
--------
ab
(1 row)

Time: 20,812 ms
postgres=# select concat('a',10);
ERROR: function concat(unknown, integer) does not exist
LINE 1: select concat('a',10);
^
HINT: No function matches the given name and argument types. You
might need to add explicit type casts.

so with variadic "any"[] concat doesn't need explicit cats.

Regards

Pavel Stehule

p.s. inside function is every value transformed to text.

Show quoted text

merlin

#41Robert Haas
robertmhaas@gmail.com
In reply to: Merlin Moncure (#39)
Re: patch (for 9.1) string functions

On Mon, Jul 26, 2010 at 9:10 AM, Merlin Moncure <mmoncure@gmail.com> wrote:

CONCAT('foo', NULL) => 'foo' really the behavior that everyone else
implements here?  And why does CONCAT() take a variadic "ANY"
argument?  Shouldn't that be variadic TEXT?

What does that accomplish, besides forcing you to sprinkle every
concat call with text casts (maybe that's not a bad thing?)?

You could ask the same thing about the existing || operator. And in
fact, we used to have that behavior. We changed it in 8.3. Perhaps
that was a good decision and perhaps it wasn't, but I don't think
using CONCAT() to make an end-run around that decision is the way to
go.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#42Merlin Moncure
mmoncure@gmail.com
In reply to: Robert Haas (#41)
Re: patch (for 9.1) string functions

On Mon, Jul 26, 2010 at 9:26 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Jul 26, 2010 at 9:10 AM, Merlin Moncure <mmoncure@gmail.com> wrote:

CONCAT('foo', NULL) => 'foo' really the behavior that everyone else
implements here?  And why does CONCAT() take a variadic "ANY"
argument?  Shouldn't that be variadic TEXT?

What does that accomplish, besides forcing you to sprinkle every
concat call with text casts (maybe that's not a bad thing?)?

You could ask the same thing about the existing || operator.  And in
fact, we used to have that behavior.  We changed it in 8.3.  Perhaps
that was a good decision and perhaps it wasn't, but I don't think
using CONCAT() to make an end-run around that decision is the way to
go.

It was absolutely a good decision because it prevented type inference
in ways that were ambiguous or surprising (for a canonical case see:
http://www.mail-archive.com/pgsql-general@postgresql.org/msg93224.html).

|| operator is still pretty tolerant in the 8.3+ world.
select interval_col || bool_col; -- error
select interval_col::text || bool_col; -- text concatenation
select text_col || interval_col || bool_col; -- text concatenation

variadic text would require text casts on EVERY non 'unknown' argument
which drops it below the threshold of usefulness IMO -- it would be
far stricter than vanilla || concatenation. Ok, pure bikeshed here
(shutting my trap now!), but concat() is one of those wonder functions
that you want to make as usable and terse as possible. I don't see
the value in making it overly strict.

merlin

#43Robert Haas
robertmhaas@gmail.com
In reply to: Merlin Moncure (#42)
Re: patch (for 9.1) string functions

On Mon, Jul 26, 2010 at 10:39 AM, Merlin Moncure <mmoncure@gmail.com> wrote:

On Mon, Jul 26, 2010 at 9:26 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Jul 26, 2010 at 9:10 AM, Merlin Moncure <mmoncure@gmail.com> wrote:

CONCAT('foo', NULL) => 'foo' really the behavior that everyone else
implements here?  And why does CONCAT() take a variadic "ANY"
argument?  Shouldn't that be variadic TEXT?

What does that accomplish, besides forcing you to sprinkle every
concat call with text casts (maybe that's not a bad thing?)?

You could ask the same thing about the existing || operator.  And in
fact, we used to have that behavior.  We changed it in 8.3.  Perhaps
that was a good decision and perhaps it wasn't, but I don't think
using CONCAT() to make an end-run around that decision is the way to
go.

It was absolutely a good decision because it prevented type inference
in ways that were ambiguous or surprising (for a canonical case see:
http://www.mail-archive.com/pgsql-general@postgresql.org/msg93224.html).

|| operator is still pretty tolerant in the 8.3+ world.
select interval_col || bool_col; -- error
select interval_col::text || bool_col; -- text concatenation
select text_col || interval_col || bool_col; -- text concatenation

variadic text would require text casts on EVERY non 'unknown' argument
which drops it below the threshold of usefulness IMO -- it would be
far stricter than vanilla || concatenation.  Ok, pure bikeshed here
(shutting my trap now!), but concat() is one of those wonder functions
that you want to make as usable and terse as possible.  I don't see
the value in making it overly strict.

I'm just very skeptical that we should give our functions argument
types that are essentially fantasy. CONCAT() doesn't concatenate
integers or intervals or boxes: it concatenates strings, and only
strings. Surely the right fix, if our casting rules are too
restrictive, is to fix the casting rules; not to start adding a bunch
of kludgery in every function we define.

The problem with your canonical example (and the other examples of
this type I've seen) is that they are underspecified. Given two
identically-named operators, one of which accepts types T1 and T2, and
the other of which accepts types T3 and T4, what is one to do with T1
OP T4? You can cast T1 to T3 and call the first operator or you can
cast T4 to T2 and call the second one, and it's really not clear which
is right, so you had better thrown an error. The same applies to
functions: given foo(text) and foo(date) and the call
foo('2010-04-15'), you had better complain, or you may end up with a
very sad user. But sometimes our current casting rules require casts
in situations where they don't seem necessary, such as
LPAD(integer_column, '0', 5). That's not ambiguous because there's
only one definition of LPAD, and the chances that the user will be
unpleasantly surprised if you call it seem quite low.

In other words, this problem is not unique to CONCAT().

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#44Merlin Moncure
mmoncure@gmail.com
In reply to: Robert Haas (#43)
Re: patch (for 9.1) string functions

On Mon, Jul 26, 2010 at 11:07 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Jul 26, 2010 at 10:39 AM, Merlin Moncure <mmoncure@gmail.com> wrote:

It was absolutely a good decision because it prevented type inference
in ways that were ambiguous or surprising (for a canonical case see:
http://www.mail-archive.com/pgsql-general@postgresql.org/msg93224.html).

|| operator is still pretty tolerant in the 8.3+ world.
select interval_col || bool_col; -- error
select interval_col::text || bool_col; -- text concatenation
select text_col || interval_col || bool_col; -- text concatenation

variadic text would require text casts on EVERY non 'unknown' argument
which drops it below the threshold of usefulness IMO -- it would be
far stricter than vanilla || concatenation.  Ok, pure bikeshed here
(shutting my trap now!), but concat() is one of those wonder functions
that you want to make as usable and terse as possible.  I don't see
the value in making it overly strict.

I'm just very skeptical that we should give our functions argument
types that are essentially fantasy.  CONCAT() doesn't concatenate
integers or intervals or boxes: it concatenates strings, and only
strings.  Surely the right fix, if our casting rules are too
restrictive, is to fix the casting rules; not to start adding a bunch
of kludgery in every function we define.

The problem with your canonical example (and the other examples of
this type I've seen) is that they are underspecified.  Given two
identically-named operators, one of which accepts types T1 and T2, and
the other of which accepts types T3 and T4, what is one to do with T1
OP T4?  You can cast T1 to T3 and call the first operator or you can
cast T4 to T2 and call the second one, and it's really not clear which
is right, so you had better thrown an error.  The same applies to
functions: given foo(text) and foo(date) and the call
foo('2010-04-15'), you had better complain, or you may end up with a
very sad user.  But sometimes our current casting rules require casts
in situations where they don't seem necessary, such as
LPAD(integer_column, '0', 5).  That's not ambiguous because there's
only one definition of LPAD, and the chances that the user will be
unpleasantly surprised if you call it seem quite low.

In other words, this problem is not unique to CONCAT().

shoot, can't resist :-).

Are the casting rules broken? If you want to do lpad w/o casts for
integers, you can define it explicitly -- feature, not bug. You can
basically do this for any function with fixed arguments -- either in
userland or core. lpad(int) actually introduces a problem case with
the minus sign possibly requiring application specific intervention,
so things are probably correct exactly as they are. Casting rules
need to be tight because if they are not they can introduce
independent behaviors when you underspecify as you noted above.

ISTM you are unhappy with the "any" variadic mechanism in general, not
the casting rules. "any" essentially means: "the types you pass to me
are irrelevant, because i'm going to look up behaviors on the server
and apply them as I see fit". You've defined a regular methodology
across ALL types and allow the user to pass anything -- I see nothing
at all wrong with this as long as it's only implemented in very
special cases. If you happen to not like the predefined 'box'
behavior, nothing is stopping you from massaging it before sending it
in. Also, there's no guarantee that the behavior hidden under the
"any" can be reproduced via manual cast...concat() is some thing of a
special case where you can.

merlin

#45Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#43)
Re: patch (for 9.1) string functions

2010/7/26 Robert Haas <robertmhaas@gmail.com>:

On Mon, Jul 26, 2010 at 10:39 AM, Merlin Moncure <mmoncure@gmail.com> wrote:

On Mon, Jul 26, 2010 at 9:26 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Jul 26, 2010 at 9:10 AM, Merlin Moncure <mmoncure@gmail.com> wrote:

CONCAT('foo', NULL) => 'foo' really the behavior that everyone else
implements here?  And why does CONCAT() take a variadic "ANY"
argument?  Shouldn't that be variadic TEXT?

What does that accomplish, besides forcing you to sprinkle every
concat call with text casts (maybe that's not a bad thing?)?

You could ask the same thing about the existing || operator.  And in
fact, we used to have that behavior.  We changed it in 8.3.  Perhaps
that was a good decision and perhaps it wasn't, but I don't think
using CONCAT() to make an end-run around that decision is the way to
go.

It was absolutely a good decision because it prevented type inference
in ways that were ambiguous or surprising (for a canonical case see:
http://www.mail-archive.com/pgsql-general@postgresql.org/msg93224.html).

|| operator is still pretty tolerant in the 8.3+ world.
select interval_col || bool_col; -- error
select interval_col::text || bool_col; -- text concatenation
select text_col || interval_col || bool_col; -- text concatenation

variadic text would require text casts on EVERY non 'unknown' argument
which drops it below the threshold of usefulness IMO -- it would be
far stricter than vanilla || concatenation.  Ok, pure bikeshed here
(shutting my trap now!), but concat() is one of those wonder functions
that you want to make as usable and terse as possible.  I don't see
the value in making it overly strict.

I'm just very skeptical that we should give our functions argument
types that are essentially fantasy.  CONCAT() doesn't concatenate
integers or intervals or boxes: it concatenates strings, and only
strings.  Surely the right fix, if our casting rules are too
restrictive, is to fix the casting rules; not to start adding a bunch
of kludgery in every function we define.

Rules are correct probably. The problem is in searching function
algorithm - it is too simple (and fast, just only one cycle). And some
exceptions - like COALESCE and similar are solved specifically on
parser level. We cannot enforce some casting on user level. PostgreSQL
is more strict with timestamps, dates than other databases. Sometimes
you have to do explicit casts, but it clean from context desired
datatype -

SELECT date_trunc('day', current_date) - result is timestamp, but it
is clean, so result have to be date ... When I proposed a parser hook
I though about these functions. With this hook, you can enforce any
necessary casting like some buildin functions does.

so "any"

Show quoted text

The problem with your canonical example (and the other examples of
this type I've seen) is that they are underspecified.  Given two
identically-named operators, one of which accepts types T1 and T2, and
the other of which accepts types T3 and T4, what is one to do with T1
OP T4?  You can cast T1 to T3 and call the first operator or you can
cast T4 to T2 and call the second one, and it's really not clear which
is right, so you had better thrown an error.  The same applies to
functions: given foo(text) and foo(date) and the call
foo('2010-04-15'), you had better complain, or you may end up with a
very sad user.  But sometimes our current casting rules require casts
in situations where they don't seem necessary, such as
LPAD(integer_column, '0', 5).  That's not ambiguous because there's
only one definition of LPAD, and the chances that the user will be
unpleasantly surprised if you call it seem quite low.

In other words, this problem is not unique to CONCAT().

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#46Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#45)
Re: patch (for 9.1) string functions

so "any" datatype is last possibility - is workaroud for custom functions.

Probably the most correct implementation of CONCAT have to contains a
parser changes - and then you can use a some internal concat function
with text only parameters. VARIADIC with "any" is just workaround that
is enouhg

Regards

Pavel

Show quoted text

The problem with your canonical example (and the other examples of
this type I've seen) is that they are underspecified.  Given two
identically-named operators, one of which accepts types T1 and T2, and
the other of which accepts types T3 and T4, what is one to do with T1
OP T4?  You can cast T1 to T3 and call the first operator or you can
cast T4 to T2 and call the second one, and it's really not clear which
is right, so you had better thrown an error.  The same applies to
functions: given foo(text) and foo(date) and the call
foo('2010-04-15'), you had better complain, or you may end up with a
very sad user.  But sometimes our current casting rules require casts
in situations where they don't seem necessary, such as
LPAD(integer_column, '0', 5).  That's not ambiguous because there's
only one definition of LPAD, and the chances that the user will be
unpleasantly surprised if you call it seem quite low.

In other words, this problem is not unique to CONCAT().

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#47Robert Haas
robertmhaas@gmail.com
In reply to: Merlin Moncure (#44)
Re: patch (for 9.1) string functions

On Mon, Jul 26, 2010 at 11:39 AM, Merlin Moncure <mmoncure@gmail.com> wrote:

I'm just very skeptical that we should give our functions argument
types that are essentially fantasy.  CONCAT() doesn't concatenate
integers or intervals or boxes: it concatenates strings, and only
strings.  Surely the right fix, if our casting rules are too
restrictive, is to fix the casting rules; not to start adding a bunch
of kludgery in every function we define.

The problem with your canonical example (and the other examples of
this type I've seen) is that they are underspecified.  Given two
identically-named operators, one of which accepts types T1 and T2, and
the other of which accepts types T3 and T4, what is one to do with T1
OP T4?  You can cast T1 to T3 and call the first operator or you can
cast T4 to T2 and call the second one, and it's really not clear which
is right, so you had better thrown an error.  The same applies to
functions: given foo(text) and foo(date) and the call
foo('2010-04-15'), you had better complain, or you may end up with a
very sad user.  But sometimes our current casting rules require casts
in situations where they don't seem necessary, such as
LPAD(integer_column, '0', 5).  That's not ambiguous because there's
only one definition of LPAD, and the chances that the user will be
unpleasantly surprised if you call it seem quite low.

In other words, this problem is not unique to CONCAT().

shoot, can't resist :-).

Are the casting rules broken? If you want to do lpad w/o casts for
integers, you can define it explicitly -- feature, not bug.  You can
basically do this for any function with fixed arguments -- either in
userland or core.  lpad(int) actually introduces a problem case with
the minus sign possibly requiring application specific intervention,
so things are probably correct exactly as they are.

Huh? If you're arguing that LPAD should require a cast on an integer
argument because the defined behavior of the function might not be
what someone wants, then apparently explicit casts should be required
in all cases. If you're arguing that I can eliminate the need for an
explicit cast by overloading LPAD(), I agree with you, but that's not
a feature.

ISTM you are unhappy with the "any" variadic mechanism in general, not
the casting rules.

No, I'm unhappy with the use of "any" to make an end-run around the
casting rules. If you're writing a function that operates on an
argument of any type, like pg_sizeof() - or if you're writing a
function that does something like append two arrays of unspecified but
identical type or, perhaps, search an array of unspecified type for an
element of that same type - or if you're writing a function where the
types of the arguments can't be known in advance, like printf(), well,
then any is what you need. But the only argument anyone has put
forward for making CONCAT() accept ANY instead of TEXT is that it
might require casting otherwise. My response to that is "well then
you have to cast it, or fix the casting rules".

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#48Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#47)
Re: patch (for 9.1) string functions

2010/7/26 Robert Haas <robertmhaas@gmail.com>:

On Mon, Jul 26, 2010 at 11:39 AM, Merlin Moncure <mmoncure@gmail.com> wrote:

I'm just very skeptical that we should give our functions argument
types that are essentially fantasy.  CONCAT() doesn't concatenate
integers or intervals or boxes: it concatenates strings, and only
strings.  Surely the right fix, if our casting rules are too
restrictive, is to fix the casting rules; not to start adding a bunch
of kludgery in every function we define.

The problem with your canonical example (and the other examples of
this type I've seen) is that they are underspecified.  Given two
identically-named operators, one of which accepts types T1 and T2, and
the other of which accepts types T3 and T4, what is one to do with T1
OP T4?  You can cast T1 to T3 and call the first operator or you can
cast T4 to T2 and call the second one, and it's really not clear which
is right, so you had better thrown an error.  The same applies to
functions: given foo(text) and foo(date) and the call
foo('2010-04-15'), you had better complain, or you may end up with a
very sad user.  But sometimes our current casting rules require casts
in situations where they don't seem necessary, such as
LPAD(integer_column, '0', 5).  That's not ambiguous because there's
only one definition of LPAD, and the chances that the user will be
unpleasantly surprised if you call it seem quite low.

In other words, this problem is not unique to CONCAT().

shoot, can't resist :-).

Are the casting rules broken? If you want to do lpad w/o casts for
integers, you can define it explicitly -- feature, not bug.  You can
basically do this for any function with fixed arguments -- either in
userland or core.  lpad(int) actually introduces a problem case with
the minus sign possibly requiring application specific intervention,
so things are probably correct exactly as they are.

Huh?  If you're arguing that LPAD should require a cast on an integer
argument because the defined behavior of the function might not be
what someone wants, then apparently explicit casts should be required
in all cases.  If you're arguing that I can eliminate the need for an
explicit cast by overloading LPAD(), I agree with you, but that's not
a feature.

ISTM you are unhappy with the "any" variadic mechanism in general, not
the casting rules.

No, I'm unhappy with the use of "any" to make an end-run around the
casting rules.  If you're writing a function that operates on an
argument of any type, like pg_sizeof() - or if you're writing a
function that does something like append two arrays of unspecified but
identical type or, perhaps, search an array of unspecified type for an
element of that same type - or if you're writing a function where the
types of the arguments can't be known in advance, like printf(), well,
then any is what you need.  But the only argument anyone has put
forward for making CONCAT() accept ANY instead of TEXT is that it
might require casting otherwise.  My response to that is "well then
you have to cast it, or fix the casting rules".

I understand, but with only text accepting, then CONCAT will has much
less benefit - you can't do a numeric list, for example

see concat(c1::text, ',', c2::text, ',' ...)

with this is much simpler use a pipes '' || c1 || ',' || c2 ... and
this operator does necessary cast self.

This function is probably one use case of exception from our rules.

Regards

Pavel Stehule

Show quoted text

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#49Robert Haas
robertmhaas@gmail.com
In reply to: Pavel Stehule (#48)
Re: patch (for 9.1) string functions

On Mon, Jul 26, 2010 at 2:09 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I understand, but with only text accepting, then CONCAT will has much
less benefit - you can't do a numeric list, for example

see concat(c1::text, ',', c2::text, ',' ...)

with this is much simpler use a pipes '' || c1 || ',' || c2 ... and
this operator does necessary cast self.

This function is probably one use case of exception from our rules.

At least two, right? Because for that use case you'd probably want
concat_ws(). In fact, it's hard for me to think of a variadic text
function where you wouldn't want the "no casts" behavior you're
getting via ANY.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#50Merlin Moncure
mmoncure@gmail.com
In reply to: Robert Haas (#49)
Re: patch (for 9.1) string functions

On Mon, Jul 26, 2010 at 3:07 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Jul 26, 2010 at 2:09 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I understand, but with only text accepting, then CONCAT will has much
less benefit - you can't do a numeric list, for example

see concat(c1::text, ',', c2::text, ',' ...)

with this is much simpler use a pipes '' || c1 || ',' || c2 ... and
this operator does necessary cast self.

This function is probably one use case of exception from our rules.

At least two, right?

correct: there would be at least two.

 Because for that use case you'd probably want
concat_ws().  In fact, it's hard for me to think of a variadic text
function where you wouldn't want the "no casts" behavior you're
getting via ANY.

concat() is not a variadic text function. it is variadic "any" that
happens to do text coercion (not casting) inside the function. The
the assumption that concat is casting internally is probably wrong.
Suppose I had hacked the int->text cast to call a custom function -- I
would very much expect concat() not to produce output from that
function, just the vanilla output text (I could always force the cast
if I wanted to).

concat is just a function that does something highly similar to
casting. suppose I had a function count_memory(variadic "any") that
summed memory usage of input args -- forcing casts would make no sense
in that context (I'm not suggesting that you think so -- just bringing
up a case that illustrates how forcing cast into the function can
change behavior in subtle ways).

merlin

#51Robert Haas
robertmhaas@gmail.com
In reply to: Merlin Moncure (#50)
Re: patch (for 9.1) string functions

On Mon, Jul 26, 2010 at 3:49 PM, Merlin Moncure <mmoncure@gmail.com> wrote:

concat() is not a variadic text function. it is variadic "any" that
happens to do text coercion (not casting) inside the function. The
the assumption that concat is casting internally is probably wrong.
Suppose I had hacked the int->text cast to call a custom function -- I
would very much expect concat() not to produce output from that
function, just the vanilla output text (I could always force the cast
if I wanted to).

concat is just a function that does something highly similar to
casting.  suppose I had a function count_memory(variadic "any") that
summed memory usage of input args -- forcing casts would make no sense
in that context (I'm not suggesting that you think so -- just bringing
up a case that illustrates how forcing cast into the function can
change behavior in subtle ways).

Right, but I already said I wasn't objecting to the use of variadic
ANY in cases like that - only in cases where, as here, you were
basically taking any old arguments and forcing them all to text.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#52Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#49)
Re: patch (for 9.1) string functions

2010/7/26 Robert Haas <robertmhaas@gmail.com>:

On Mon, Jul 26, 2010 at 2:09 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I understand, but with only text accepting, then CONCAT will has much
less benefit - you can't do a numeric list, for example

see concat(c1::text, ',', c2::text, ',' ...)

with this is much simpler use a pipes '' || c1 || ',' || c2 ... and
this operator does necessary cast self.

This function is probably one use case of exception from our rules.

At least two, right?  Because for that use case you'd probably want
concat_ws().

sorry, yes

Pavel

 In fact, it's hard for me to think of a variadic text

Show quoted text

function where you wouldn't want the "no casts" behavior you're
getting via ANY.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#53Erik Rijkers
er@xs4all.nl
In reply to: Pavel Stehule (#48)
1 attachment(s)
Re: patch (for 9.1) string functions

Hi Pavel,

In xfunc.sgml, I came across a function example (for use of VARIADIC in polymorphic functions),
where the function name is concat(): (in the manual: 35.4.10. Polymorphic SQL Functions).
Although that is not strictly wrong, it seems better to change that name when concat goes into
core, as seems to be the plan.

If you agree, it seems best to include this change in your patch and change that example
function's name when the stringfunc patch gets applied.

Erik Rijkers

Attachments:

xfunc.sgml.diffapplication/octet-stream; name=xfunc.sgml.diffDownload
--- doc/src/sgml/xfunc.sgml.orig	2010-05-14 23:00:23.000000000 +0200
+++ doc/src/sgml/xfunc.sgml	2010-05-15 01:52:52.000000000 +0200
@@ -859,7 +859,8 @@
      output parameters, like this:
 
 <programlisting>
-CREATE FUNCTION sum_n_product_with_tab (x int, OUT sum int, OUT product int) RETURNS SETOF record AS $$
+CREATE FUNCTION sum_n_product_with_tab (x int, OUT sum int, OUT product int)
+RETURNS SETOF record AS $$
     SELECT $1 + tab.y, $1 * tab.y FROM tab;
 $$ LANGUAGE SQL;
 </programlisting>
@@ -957,7 +958,8 @@
      done this way:
 
 <programlisting>
-CREATE FUNCTION sum_n_product_with_tab (x int) RETURNS TABLE(sum int, product int) AS $$
+CREATE FUNCTION sum_n_product_with_tab (x int)
+RETURNS TABLE(sum int, product int) AS $$
     SELECT $1 + tab.y, $1 * tab.y FROM tab;
 $$ LANGUAGE SQL;
 </programlisting>
@@ -1026,7 +1028,8 @@
     SELECT 1;
 $$ LANGUAGE SQL;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning a polymorphic type must have at least one polymorphic argument.
+DETAIL:  A function returning a polymorphic type must have 
+                                         at least one polymorphic argument.
 </screen>
     </para>
 
@@ -2075,7 +2078,7 @@
 <programlisting>
 PG_FUNCTION_INFO_V1(funcname);
 </programlisting>
-     must appear in the same source file.  (Conventionally. it's
+     must appear in the same source file.  (Conventionally, it's
      written just before the function itself.)  This macro call is not
      needed for <literal>internal</>-language functions, since
      <productname>PostgreSQL</> assumes that all internal functions
#54Erik Rijkers
er@xs4all.nl
In reply to: Erik Rijkers (#53)
1 attachment(s)
Re: patch (for 9.1) string functions ( correct patch attached )

On Thu, July 29, 2010 22:43, Erik Rijkers wrote:

Hi Pavel,

In xfunc.sgml, I came across a function example (for use of VARIADIC in polymorphic functions),
where the function name is concat(): (in the manual: 35.4.10. Polymorphic SQL Functions).
Although that is not strictly wrong, it seems better to change that name when concat goes into
core, as seems to be the plan.

If you agree, it seems best to include this change in your patch and change that example
function's name when the stringfunc patch gets applied.

My apologies, the previous email had the wrong doc-patch attached.

Here is the correct one.

Erik Rijkers

Attachments:

xfunc.sgml.diffapplication/octet-stream; name=xfunc.sgml.diffDownload
--- xfunc.sgml.orig	2010-07-29 22:18:27.000000000 +0200
+++ xfunc.sgml	2010-07-29 22:32:53.000000000 +0200
@@ -1067,13 +1067,13 @@
  abc
 (1 row)
 
-CREATE FUNCTION concat(text, VARIADIC anyarray) RETURNS text AS $$
+CREATE FUNCTION concat_values(text, VARIADIC anyarray) RETURNS text AS $$
     SELECT array_to_string($2, $1);
 $$ LANGUAGE SQL;
 
-SELECT concat('|', 1, 4, 2);
- concat 
---------
+SELECT concat_values('|', 1, 4, 2);
+ concat_values 
+---------------
  1|4|2
 (1 row)
 </screen>
#55Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#51)
Re: patch (for 9.1) string functions

On Mon, Jul 26, 2010 at 8:48 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Jul 26, 2010 at 3:49 PM, Merlin Moncure <mmoncure@gmail.com> wrote:

concat() is not a variadic text function. it is variadic "any" that
happens to do text coercion (not casting) inside the function. The
the assumption that concat is casting internally is probably wrong.
Suppose I had hacked the int->text cast to call a custom function -- I
would very much expect concat() not to produce output from that
function, just the vanilla output text (I could always force the cast
if I wanted to).

concat is just a function that does something highly similar to
casting.  suppose I had a function count_memory(variadic "any") that
summed memory usage of input args -- forcing casts would make no sense
in that context (I'm not suggesting that you think so -- just bringing
up a case that illustrates how forcing cast into the function can
change behavior in subtle ways).

Right, but I already said I wasn't objecting to the use of variadic
ANY in cases like that - only in cases where, as here, you were
basically taking any old arguments and forcing them all to text.

I believe that another unpleasant side effect of this is that CONCAT()
will have to be declared stable rather than immutable.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#56Pavel Stehule
pavel.stehule@gmail.com
In reply to: Erik Rijkers (#54)
Re: patch (for 9.1) string functions ( correct patch attached )

Hello

2010/7/29 Erik Rijkers <er@xs4all.nl>:

On Thu, July 29, 2010 22:43, Erik Rijkers wrote:

Hi Pavel,

In xfunc.sgml, I came across a function example (for use of VARIADIC in polymorphic functions),
where the function name is concat():  (in the manual: 35.4.10. Polymorphic SQL Functions).
Although that is not strictly wrong, it seems better to change that name when concat goes into
core, as seems to be the plan.

If you agree, it seems best to include this change in your patch and change that example
function's name when the stringfunc patch gets applied.

My apologies, the previous email had the wrong doc-patch attached.

Here is the correct one.

it is good idea, thank you

Pavel

Show quoted text

Erik Rijkers

#57Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Robert Haas (#38)
Re: patch (for 9.1) string functions

2010/7/26 Robert Haas <robertmhaas@gmail.com>:

Come to think of it, have we checked that the behavior of LEFT, RIGHT,
REVERSE, etc. is the same on other DBs, especially as far as nulls,
empty strings, too-large or negative subscripts, etc is concerned?  Is
CONCAT('foo', NULL) => 'foo' really the behavior that everyone else
implements here?

I made a discussion page in wiki for the compatibility issue.
http://wiki.postgresql.org/wiki/String_Functions_and_Operators_Compatibility

Please fill empty cells and fix wrong descriptions.
* concat() is not compatible between MySQL and Oracle/DB2. Which do we buy?
* How do other databases behave in left() and right() with negative lengths?
* Are there any databases that has similar features with format() or
sprintf() ?

 And why does CONCAT() take a variadic "ANY"
argument?  Shouldn't that be variadic TEXT?

I think we have no other choice but to use VARIADIC "any" for variadic
functions.
We have all combinations of argument types for || operator, (text, text),
(text, any), (any, text), but we cannot use such codes for variadic functions
-- they have no limits of argument numbers. And in the case, the functions
should be STABLE because they convert arguments to text in it with typout
functions that might be STABLE.

IMHO, I'd repeat, syntax for format() is a bad choice because it cannot
concatenate multiple arguments without separator, though RAISE also uses it.
%s format in sprintf() or {n} syntax in C#'s String.Format() seems to be
a better design.

--
Itagaki Takahiro

#58Pavel Stehule
pavel.stehule@gmail.com
In reply to: Itagaki Takahiro (#57)
Re: patch (for 9.1) string functions

Hello

2010/8/7 Itagaki Takahiro <itagaki.takahiro@gmail.com>:

2010/7/26 Robert Haas <robertmhaas@gmail.com>:

Come to think of it, have we checked that the behavior of LEFT, RIGHT,
REVERSE, etc. is the same on other DBs, especially as far as nulls,
empty strings, too-large or negative subscripts, etc is concerned?  Is
CONCAT('foo', NULL) => 'foo' really the behavior that everyone else
implements here?

I made a discussion page in wiki for the compatibility issue.
http://wiki.postgresql.org/wiki/String_Functions_and_Operators_Compatibility

nice, thank you

Please fill empty cells and fix wrong descriptions.
 * concat() is not compatible between MySQL and Oracle/DB2. Which do we buy?

I prefer a our implementation - it skip a NULL values and it has a
variadic arguments. MySQL's concat isn't too consistent - I don't know
why it has different NULL handlidg than concat_ws.

 * How do other databases behave in left() and right() with negative lengths?

I don't know about one with left() and right() functions. What I know,
only MS Access has these functions. The design of these functions is
inspirited by wide used a Oracle library PLvision - this library is
freeware now - but my code is original. See plvstr.left() and
plvstr.right() - and little bit by python substring operations. The
sense of negative arguments is elimination of necessary detoast
operations and utf8 related calculations. For right() it means skip
first n chars, for left() skip last n chars. These functions was
originally designed for contrib - and I still thinking so contrib is
better - My opinion isn't strong here - I prefer a fully functional
function in contrib before minimalistic version in core. Minimalistic
functions are trivial via substring.

 * Are there any databases that has similar features with format() or
sprintf() ?

I know only about package from PLvision library -

select plvsubst.string('My name is %s %s', ARRAY['Pavel','Stěhule']);

but you can find a lot of custom implementations. I found a some
similar - not exactly this in T-SQL see FORMATMESSAGE() function. But
the using of this function is very limited and it is C API function
(available from T-SQL). It doesn't return a string, just write to log.

 And why does CONCAT() take a variadic "ANY"
argument?  Shouldn't that be variadic TEXT?

I think we have no other choice but to use VARIADIC "any" for variadic
functions.
We have all combinations of argument types for || operator, (text, text),
(text, any), (any, text), but we cannot use such codes for variadic functions
-- they have no limits of argument numbers. And in the case, the functions
should be STABLE because they convert arguments to text in it with typout
functions that might be STABLE.

IMHO, I'd repeat, syntax for format() is a bad choice because it cannot
concatenate multiple arguments without separator, though RAISE also uses it.
%s format in sprintf() or {n} syntax in C#'s String.Format() seems to be
a better design.

I don't agree. This function isn't designed to replace string
concation. It is designed to build a SQL string (for dynamic SQL) or
format messages. It isn't designed to replace to_char function. It is
designed to work mainly inside PLpgSQL functions and then is
consistent with RAISE statement.

Thank you

Regards

Pavel Stehule

Show quoted text

--
Itagaki Takahiro

#59Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Pavel Stehule (#58)
Re: patch (for 9.1) string functions

On Sat, Aug 7, 2010 at 8:39 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I made a discussion page in wiki for the compatibility issue.
http://wiki.postgresql.org/wiki/String_Functions_and_Operators_Compatibility

nice, thank you

I filled cells for SQL Server and DB2.

 * concat() is not compatible between MySQL and Oracle/DB2. Which do we buy?

I prefer a our implementation - it skip a NULL values and it has a
variadic arguments.

OK. I'm going to put both concat() and concat_ws() into core.

 * How do other databases behave in left() and right() with negative lengths?

little bit by python substring operations.

I'll respect your proposal. The behaviors for negative lengths will
be our specific feature, but I don't see any problems there.
Since other databases raises errors, user should have negative-protections
in their existing codes.

I don't agree. This function isn't designed to replace string
concation. It is designed to build a SQL string (for dynamic SQL) or
format messages. It isn't designed to replace to_char function. It is
designed to work mainly inside PLpgSQL functions and then is
consistent with RAISE statement.

OK. I'll revert my changes to your original format().

But please wait a moment to include sprintf() and contrib/stringfunc.
I think the function is useful, but don't want to have two versions
of formatting functions. So, the extended features will be merged
into format() with additional syntax something like {10s}. Then,
we could simplify the code because some of complex format syntax
are not so useful in SQL, especially length+string formatter (*s).

--
Itagaki Takahiro

#60Pavel Stehule
pavel.stehule@gmail.com
In reply to: Itagaki Takahiro (#59)
Re: patch (for 9.1) string functions

2010/8/23 Itagaki Takahiro <itagaki.takahiro@gmail.com>:

On Sat, Aug 7, 2010 at 8:39 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I made a discussion page in wiki for the compatibility issue.
http://wiki.postgresql.org/wiki/String_Functions_and_Operators_Compatibility

nice, thank you

I filled cells for SQL Server and DB2.

 * concat() is not compatible between MySQL and Oracle/DB2. Which do we buy?

I prefer a our implementation - it skip a NULL values and it has a
variadic arguments.

OK. I'm going to put both concat() and concat_ws() into core.

 * How do other databases behave in left() and right() with negative lengths?

little bit by python substring operations.

I'll respect your proposal. The behaviors for negative lengths will
be our specific feature, but I don't see any problems there.
Since other databases raises errors, user should have negative-protections
in their existing codes.

I don't agree. This function isn't designed to replace string
concation. It is designed to build a SQL string (for dynamic SQL) or
format messages. It isn't designed to replace to_char function. It is
designed to work mainly inside PLpgSQL functions and then is
consistent with RAISE statement.

OK. I'll revert my changes to your original format().

But please wait a moment to include sprintf() and contrib/stringfunc.
I think the function is useful, but don't want to have two versions
of formatting functions. So, the extended features will be merged
into format() with additional syntax something like {10s}. Then,
we could simplify the code because some of complex format syntax
are not so useful in SQL, especially length+string formatter (*s).

It's reason, why I moved sprintf to contrib. When you build a SQL
statement or error message, you don't need (usually) complex
formating. And when you need it, then you can use a contrib sprintf
function. I am not against to additional simply formating - but I
afraid so we will create a second "printf" function. The formating
enhancing should be shared with RAISE NOTICE command. Probably we can
share code better now, when a PLpgSQL is in core.

Regards

Pavel

Show quoted text

--
Itagaki Takahiro

#61Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#60)
Re: patch (for 9.1) string functions

Pavel Stehule <pavel.stehule@gmail.com> writes:

... The formating
enhancing should be shared with RAISE NOTICE command.

I remain of the opinion that RAISE's approach to formatting is
completely broken and inextensible, and that any attempt to be somehow
compatible with it is going to lead to an unusably broken design.
You should leave RAISE alone and just think about printf.

regards, tom lane

#62Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#61)
Re: patch (for 9.1) string functions

2010/8/23 Tom Lane <tgl@sss.pgh.pa.us>:

Pavel Stehule <pavel.stehule@gmail.com> writes:

... The formating
enhancing should be shared with RAISE NOTICE command.

I remain of the opinion that RAISE's approach to formatting is
completely broken and inextensible, and that any attempt to be somehow
compatible with it is going to lead to an unusably broken design.
You should leave RAISE alone and just think about printf.

ok - then we don't need modify proposed patch. "Format" function is
enough for PL/pgSQL and other PL languages has own mutation of this
functions. There are not barrier for implementation as custom
function, so we can hold this function most simple.

Regards

Pavel

Show quoted text

                       regards, tom lane

#63Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Pavel Stehule (#62)
1 attachment(s)
Re: patch (for 9.1) string functions

I applied the attached patch to HEAD. concat(), concat_ws(), left(),
right(), and reverse() are in it, but format() and sprintf() are not.
It's my understanding that we don't have consensus about the best syntax
for the formatting function. We can forget about RAISE. C-like printf
syntax is the next candidate, but we should consider about other ones
restarting with a clean slate.

Anyway, the newly added functions are useful for developers especially
migrated from other database products. Thank you.

On Mon, Aug 23, 2010 at 11:41 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

2010/8/23 Tom Lane <tgl@sss.pgh.pa.us>:

You should leave RAISE alone and just think about printf.

ok - then we don't need modify proposed patch. "Format" function is
enough for PL/pgSQL and other PL languages has own mutation of this
functions. There are not barrier for implementation as custom
function, so we can hold this function most simple.

--
Itagaki Takahiro

Attachments:

stringfunc-20100824.diffapplication/octet-stream; name=stringfunc-20100824.diffDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index dda561f..e6923c3 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 1251,1256 ****
--- 1251,1262 ----
      <primary>chr</primary>
     </indexterm>
     <indexterm>
+     <primary>concat</primary>
+    </indexterm>
+    <indexterm>
+     <primary>concat_ws</primary>
+    </indexterm>
+    <indexterm>
      <primary>convert</primary>
     </indexterm>
     <indexterm>
***************
*** 1269,1274 ****
--- 1275,1283 ----
      <primary>initcap</primary>
     </indexterm>
     <indexterm>
+     <primary>left</primary>
+    </indexterm>
+    <indexterm>
      <primary>lpad</primary>
     </indexterm>
     <indexterm>
***************
*** 1296,1301 ****
--- 1305,1316 ----
      <primary>replace</primary>
     </indexterm>
     <indexterm>
+     <primary>reverse</primary>
+    </indexterm>
+    <indexterm>
+     <primary>right</primary>
+    </indexterm>
+    <indexterm>
      <primary>rpad</primary>
     </indexterm>
     <indexterm>
***************
*** 1376,1381 ****
--- 1391,1424 ----
  
        <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>
+         Concatenate all arguments. NULL arguments are ignored.
+        </entry>
+        <entry><literal>concat('abcde', 2, NULL, 22)</literal></entry>
+        <entry><literal>abcde222</literal></entry>
+       </row>
+ 
+       <row>
+        <entry>
+         <literal><function>concat_ws</function>(<parameter>sep</parameter> <type>text</type>,
+         <parameter>str</parameter> <type>"any"</type>
+         [, <parameter>str</parameter> <type>"any"</type> [, ...] ])</literal>
+        </entry>
+        <entry><type>text</type></entry>
+        <entry>
+         Concatenate all but first arguments with separators. The first
+         parameter is used as a separator. NULL arguments are ignored.
+        </entry>
+        <entry><literal>concat_ws(',', 'abcde', 2, NULL, 22)</literal></entry>
+        <entry><literal>abcde,2,22</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>
***************
*** 1466,1471 ****
--- 1509,1528 ----
        </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>
+         Return first <replaceable>n</> characters in the string. When <replaceable>n</>
+         is negative, return all but last |<replaceable>n</>| characters.
+         </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>
***************
*** 1680,1685 ****
--- 1737,1768 ----
  
        <row>
         <entry>
+         <literal><function>reverse</function>(<parameter>str</parameter>)</literal>
+        </entry>
+        <entry><type>text</type></entry>
+        <entry>
+         Return 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>
+         Return last <replaceable>n</> characters in the string. When <replaceable>n</>
+         is negative, return all but first |<replaceable>n</>| characters.
+        </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>
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 94766cd..0dd4bc1 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** string_agg_finalfn(PG_FUNCTION_ARGS)
*** 3556,3558 ****
--- 3556,3704 ----
  	else
  		PG_RETURN_NULL();
  }
+ 
+ static text *
+ concat_internal(const char *sepstr, int seplen, int argidx, FunctionCallInfo fcinfo)
+ {
+ 	StringInfoData	str;
+ 	text		   *result;
+ 	int				i;
+ 
+ 	initStringInfo(&str);
+ 
+ 	for (i = argidx; i < PG_NARGS(); i++)
+ 	{
+ 		if (!PG_ARGISNULL(i))
+ 		{
+ 			Oid		valtype;
+ 			Datum	value;
+ 			Oid		typOutput;
+ 			bool	typIsVarlena;
+ 
+ 			if (i > argidx)
+ 				appendBinaryStringInfo(&str, sepstr, seplen);
+ 
+ 			/* 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);
+ 
+ 	return result;
+ }
+ 
+ /*
+  * Concatenate all arguments. NULL arguments are ignored.
+  */
+ Datum
+ text_concat(PG_FUNCTION_ARGS)
+ {
+ 	PG_RETURN_TEXT_P(concat_internal(NULL, 0, 0, fcinfo));
+ }
+ 
+ /*
+  * Concatenate all but first argument values with separators. The first
+  * parameter is used as a separator. NULL arguments are ignored.
+  */
+ 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));
+ }
+ 
+ /*
+  * Return first n characters in the string. When n is negative,
+  * return all but last |n| characters.
+  */
+ Datum
+ text_left(PG_FUNCTION_ARGS)
+ {
+ 	text	   *str = PG_GETARG_TEXT_PP(0);
+ 	const char *p = VARDATA_ANY(str);
+ 	int			len = VARSIZE_ANY_EXHDR(str);
+ 	int			n = PG_GETARG_INT32(1);
+ 	int			rlen;
+ 
+ 	if (n < 0)
+ 		n = pg_mbstrlen_with_len(p, len) + n;
+ 	rlen = pg_mbcharcliplen(p, len, n);
+ 
+ 	PG_RETURN_TEXT_P(cstring_to_text_with_len(p, rlen));
+ }
+ 
+ /*
+  * Return last n characters in the string. When n is negative,
+  * return all but first |n| characters.
+  */
+ Datum
+ text_right(PG_FUNCTION_ARGS)
+ {
+ 	text	   *str = PG_GETARG_TEXT_PP(0);
+ 	const char *p = VARDATA_ANY(str);
+ 	int			len = VARSIZE_ANY_EXHDR(str);
+ 	int			n = PG_GETARG_INT32(1);
+ 	int			off;
+ 
+ 	if (n < 0)
+ 		n = -n;
+ 	else
+ 		n = pg_mbstrlen_with_len(p, len) - n;
+ 	off = pg_mbcharcliplen(p, len, n);
+ 
+ 	PG_RETURN_TEXT_P(cstring_to_text_with_len(p + off, len - off));
+ }
+ 
+ /*
+  * Return reversed string
+  */
+ Datum
+ text_reverse(PG_FUNCTION_ARGS)
+ {
+ 	text		   *str = PG_GETARG_TEXT_PP(0);
+ 	const char	   *p = VARDATA_ANY(str);
+ 	int				len = VARSIZE_ANY_EXHDR(str);
+ 	const char	   *endp = p + len;
+ 	text		   *result;
+ 	char		   *dst;
+ 
+ 	result = palloc(len + VARHDRSZ);
+ 	dst = (char*) VARDATA(result) + len;
+ 	SET_VARSIZE(result, len + VARHDRSZ);
+ 
+ 	if (pg_database_encoding_max_length() > 1)
+ 	{
+ 		/* multibyte version */
+ 		while (p < endp)
+ 		{
+ 			int		sz;
+ 
+ 			sz = pg_mblen(p);
+ 			dst -= sz;
+ 			memcpy(dst, p, sz);
+ 			p += sz;
+ 		}
+ 	}
+ 	else
+ 	{
+ 		/* single byte version */
+ 		while (p < endp)
+ 			*(--dst) = *p++;
+ 	}
+ 
+ 	PG_RETURN_TEXT_P(result);
+ }
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 1c53418..f6364cd 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("I/O");
*** 2722,2727 ****
--- 2722,2737 ----
  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 = 3058 ( 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("concatenate values");
+ DATA(insert OID = 3059 ( concat_ws	PGNSP PGUID 12 1 0 2276 f f f f f s 2 0 25 "25 2276" "{25,2276}" "{i,v}" _null_ _null_  text_concat_ws _null_ _null_ _null_ ));
+ DESCR("concatenate values with separators");
+ DATA(insert OID = 3060 ( 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("return the first n characters");
+ DATA(insert OID = 3061 ( 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("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 = 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");
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index a4c6180..f064a89 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum pg_column_size(PG_FUNCTION_
*** 733,738 ****
--- 733,744 ----
  extern Datum string_agg_transfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_finalfn(PG_FUNCTION_ARGS);
  
+ extern Datum text_concat(PG_FUNCTION_ARGS);
+ extern Datum text_concat_ws(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);
  
diff --git a/src/test/regress/expected/text.out b/src/test/regress/expected/text.out
index 08d002f..84f4a5c 100644
*** a/src/test/regress/expected/text.out
--- b/src/test/regress/expected/text.out
*************** ERROR:  operator does not exist: integer
*** 51,53 ****
--- 51,120 ----
  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.
+ /*
+  * string functions
+  */
+ select concat('one');
+  concat 
+ --------
+  one
+ (1 row)
+ 
+ select concat(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+         concat        
+ ----------------------
+  123hellotf03-09-2010
+ (1 row)
+ 
+ select concat_ws('#','one');
+  concat_ws 
+ -----------
+  one
+ (1 row)
+ 
+ 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)
+ 
+ select concat_ws(',',10,20,null,30);
+  concat_ws 
+ -----------
+  10,20,30
+ (1 row)
+ 
+ select concat_ws('',10,20,null,30);
+  concat_ws 
+ -----------
+  102030
+ (1 row)
+ 
+ select concat_ws(NULL,10,20,null,30) is null;
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ select reverse('abcde');
+  reverse 
+ ---------
+  edcba
+ (1 row)
+ 
+ select i, left('ahoj', i), right('ahoj', i) from generate_series(-5, 5) t(i) order by i;
+  i  | left | right 
+ ----+------+-------
+  -5 |      | 
+  -4 |      | 
+  -3 | a    | j
+  -2 | ah   | oj
+  -1 | aho  | hoj
+   0 |      | 
+   1 | a    | j
+   2 | ah   | oj
+   3 | aho  | hoj
+   4 | ahoj | ahoj
+   5 | ahoj | ahoj
+ (11 rows)
+ 
diff --git a/src/test/regress/sql/text.sql b/src/test/regress/sql/text.sql
index b739e56..a8768ee 100644
*** a/src/test/regress/sql/text.sql
--- b/src/test/regress/sql/text.sql
*************** select 'four: ' || 2+2;
*** 28,30 ****
--- 28,43 ----
  -- but not this:
  
  select 3 || 4.0;
+ 
+ /*
+  * string functions
+  */
+ select concat('one');
+ select concat(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ select concat_ws('#','one');
+ select concat_ws('#',1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD'));
+ select concat_ws(',',10,20,null,30);
+ select concat_ws('',10,20,null,30);
+ 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;
#64Pavel Stehule
pavel.stehule@gmail.com
In reply to: Itagaki Takahiro (#63)
Re: patch (for 9.1) string functions

2010/8/24 Itagaki Takahiro <itagaki.takahiro@gmail.com>:

I applied the attached patch to HEAD. concat(), concat_ws(), left(),
right(), and reverse() are in it, but format() and sprintf() are not.
It's my understanding that we don't have consensus about the best syntax
for the formatting function. We can forget about RAISE. C-like printf
syntax is the next candidate, but we should consider about other ones
restarting with a clean slate.

Thank you very much

C-like printf function is the most worse candidate - I don't like to
repeat discussion again - this function is designed for totally
different environment than SQL. I am sure, so we don't need a function
with complex formatting - maintaining "to_char" is good example.

Regards

Pavel Stehule

Show quoted text

Anyway, the newly added functions are useful for developers especially
migrated from other database products. Thank you.

On Mon, Aug 23, 2010 at 11:41 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

2010/8/23 Tom Lane <tgl@sss.pgh.pa.us>:

You should leave RAISE alone and just think about printf.

ok - then we don't need modify proposed patch. "Format" function is
enough for PL/pgSQL and other PL languages has own mutation of this
functions. There are not barrier for implementation as custom
function, so we can hold this function most simple.

--
Itagaki Takahiro

#65Erik Rijkers
er@xs4all.nl
In reply to: Itagaki Takahiro (#63)
Re: patch (for 9.1) string functions

On Tue, August 24, 2010 08:32, Itagaki Takahiro wrote:

I applied the attached patch to HEAD. concat(), concat_ws(), left(),
right(), and reverse() are in it, but format() and sprintf() are not.

+1 to add also sprintf

Erik Rijkers

#66Bruce Momjian
bruce@momjian.us
In reply to: Erik Rijkers (#54)
Re: patch (for 9.1) string functions ( correct patch attached )

Erik Rijkers wrote:

On Thu, July 29, 2010 22:43, Erik Rijkers wrote:

Hi Pavel,

In xfunc.sgml, I came across a function example (for use of VARIADIC in polymorphic functions),
where the function name is concat(): (in the manual: 35.4.10. Polymorphic SQL Functions).
Although that is not strictly wrong, it seems better to change that name when concat goes into
core, as seems to be the plan.

If you agree, it seems best to include this change in your patch and change that example
function's name when the stringfunc patch gets applied.

My apologies, the previous email had the wrong doc-patch attached.

Here is the correct one.

Applied for 9.1. Thanks.

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ It's impossible for everything to be true. +