patch (for 9.1) string functions
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);
+
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
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
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
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
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,twhy 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
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,twhy 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);
+
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
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
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
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);
+
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
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
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 doFOR 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
sonfusingformat() 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 ItagakiThank 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);
+
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 \
Import Notes
Resolved by subject fallback
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
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
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
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
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
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
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:463Based 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
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);
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
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 charsI 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 functionI 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);
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 charsI 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 functionI 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 NULLhave you a some idea about it?
* sprintf() has some typos in error messages
For example, "sprinf".fixed
--
Itagaki TakahiroRegards
Pavel
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;
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
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
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 muchThanks. 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
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
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 quotedThey 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
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 quotedThey 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;
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;
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 quotedI 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 yearsI 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) => ERRORSo, 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
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
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
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
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
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
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
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
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 concatenationvariadic 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
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 concatenationvariadic 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
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 concatenationvariadic 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
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
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
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
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 examplesee 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
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 examplesee 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
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
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 examplesee 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
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
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>
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
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
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
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
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_Compatibilitynice, 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
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_Compatibilitynice, 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
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
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
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;
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
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
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. +