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;