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 ---- chr + + concat + convert *************** *** 1266,1274 **** --- 1269,1283 ---- encode + format + + initcap + left + + lpad *************** *** 1296,1301 **** --- 1305,1316 ---- replace + reverse + + + right + + rpad *************** *** 1376,1381 **** --- 1391,1409 ---- + concat(str "any", + [, str "any" [...] ]) + + text + + Returns concated strings. NULLs are ignored. + + concat('abcde', 2, NULL, 22) + abcde222 + + + + convert(string bytea, src_encoding name, dest_encoding name) *************** *** 1454,1459 **** --- 1482,1502 ---- + + format(formatstr text + [, value any [...] ]) + + text + + 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 %. + + format('% % xxx: %', 10, 'foo', current_date) + 10 foo xxx: 2010-03-05 + + + initcap(string) text *************** *** 1466,1471 **** --- 1509,1528 ---- + + left(str text, + n int ) + + text + + Returns first n chars of string. When n is negative, then returns chars from begin + to n char from right. + + left('abcde', 2) + ab + + + length(string) int *************** *** 1680,1685 **** --- 1737,1768 ---- + reverse(str) + + text + + Returns reversed string. + + reverse('abcde') + edcba + + + + + right(str text, + n int ) + + text + + Returns last n chars of string. When n is negative, then returns chars from n char + to end of string + + right('abcde', 2) + de + + + + rpad(string text, length int , fill text) 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 ''. */ + appendStringInfoString(&str, ""); + } + 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;