*** ./doc/src/sgml/func.sgml.orig 2010-10-21 14:40:44.000000000 +0200
--- ./doc/src/sgml/func.sgml 2010-10-22 10:24:18.764199680 +0200
***************
*** 1272,1277 ****
--- 1272,1280 ----
encode
+ format
+
+
initcap
***************
*** 1497,1502 ****
--- 1500,1524 ----
+
+ format(formatstr text
+ [, str "any" [, ...] ])
+
+ text
+
+ This functions can be used to create a formated string or message. There are allowed
+ three types of tags: %s as string, %I as SQL identifiers and %L as SQL literals. Attention:
+ result for %I and %L must not be same as result of quote_ident and
+ quote_literal functions, because this function doesn't try to coerce
+ parameters to text type and directly use a type's output functions. The placeholder
+ can be related to some explicit parameter with using a optional n$ specification inside format.
+ See also .
+
+ format('Hello %s, %1$s', 'World')
+ Hello World, World
+
+
+
initcap(string)
text
*** ./doc/src/sgml/plpgsql.sgml.orig 2010-10-11 08:33:15.000000000 +0200
--- ./doc/src/sgml/plpgsql.sgml 2010-10-22 10:37:06.041200330 +0200
***************
*** 1250,1255 ****
--- 1250,1273 ----
must> use quote_literal>,
quote_nullable>, or quote_ident>, as appropriate.
+
+
+ Building a string with dynamic SQL statement can be simplified
+ with using a format function (see ):
+
+ EXECUTE format('UPDATE tbl SET %I = %L WHERE key = %L', colname, newvalue, keyvalue);
+
+ A use the format format can be together with
+ USING clause:
+
+ EXECUTE format('UPDATE tbl SET %I = $1 WHERE key = $2', colname)
+ USING newvalue, keyvalue;
+
+ This form is little bit more efective, because a parameters
+ newvalue and keyvalue must not be
+ converted to text.
+
*** ./src/backend/utils/adt/varlena.c.orig 2010-10-11 08:33:15.000000000 +0200
--- ./src/backend/utils/adt/varlena.c 2010-10-22 09:46:18.863075117 +0200
***************
*** 3702,3704 ****
--- 3702,3939 ----
PG_RETURN_TEXT_P(result);
}
+
+ typedef struct {
+ char field_type;
+ int32 position;
+ bool is_positional;
+ } placeholder_desc;
+
+ #define INVALID_PARAMETER_ERROR(d) ereport(ERROR, \
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE), \
+ errmsg("invalid formatting string for function format"), \
+ errdetail(d)))
+
+ static const char *
+ parse_format(placeholder_desc *pd, const char *start_ptr,
+ const char *end_ptr, int nparams)
+ {
+ bool is_valid = false;
+
+ pd->is_positional = false;
+
+ while (start_ptr <= end_ptr)
+ {
+ switch (*start_ptr)
+ {
+ case 's':
+ pd->field_type = 's';
+ is_valid = true;
+ break;
+
+ case 'I':
+ pd->field_type = 'I';
+ is_valid = true;
+ break;
+
+ case 'L':
+ pd->field_type = 'L';
+ is_valid = true;
+ break;
+
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ if (pd->is_positional)
+ INVALID_PARAMETER_ERROR("positional parameter is defined already");
+
+ pd->position = *start_ptr - '0';
+ while (start_ptr < end_ptr && isdigit(start_ptr[1]))
+ {
+ pd->position = pd->position * 10 + *++start_ptr - '0';
+ /* don't allow to integer overflow */
+ if (pd->position >= nparams)
+ INVALID_PARAMETER_ERROR("placeholder is out of range");
+ }
+
+ /* recheck position, or first check for one digit positions */
+ if (pd->position >= nparams)
+ INVALID_PARAMETER_ERROR("placeholder is out of range");
+
+ /* next character must be a $ */
+ if (start_ptr == end_ptr && isdigit(*start_ptr))
+ INVALID_PARAMETER_ERROR("unexpected end, missing '$'");
+
+ if (*++start_ptr != '$')
+ INVALID_PARAMETER_ERROR("expected '$'");
+ start_ptr++;
+
+ pd->is_positional = true;
+ break;
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid formatting string for function format"),
+ errdetail("unsupported tag \"%%%.*s\"", pg_mblen(start_ptr), start_ptr)));
+ }
+
+ if (is_valid)
+ break;
+ }
+
+ if (!is_valid)
+ INVALID_PARAMETER_ERROR("missing field type");
+
+ return start_ptr;
+ }
+
+ /*
+ * Returns a formated string
+ */
+ Datum
+ text_format(PG_FUNCTION_ARGS)
+ {
+ text *fmt;
+ StringInfoData str;
+ const char *cp;
+ int i = 1;
+ size_t len;
+ const char *start_ptr;
+ const char *end_ptr;
+ text *result;
+ bool only_ordered_parameters = true;
+
+ /* 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] == '%')
+ {
+ placeholder_desc pd = { ' ', 0, false }; /* be compiler quiet */
+ Oid valtype;
+ Datum value;
+ bool isNull;
+ int arg_num;
+ Oid typoutput;
+ bool typIsVarlena;
+
+ /* initial check */
+ if (cp == end_ptr)
+ INVALID_PARAMETER_ERROR("missing field type");
+
+ /* replace escaped symbol */
+ if (cp[1] == '%')
+ {
+ appendStringInfoChar(&str, '%');
+ cp++;
+ continue;
+ }
+
+ cp = parse_format(&pd, ++cp, end_ptr, PG_NARGS());
+
+ if (pd.is_positional)
+ {
+ arg_num = pd.position;
+ only_ordered_parameters = false;
+ }
+ else
+ {
+ /* ordered arguments are not checked yet */
+ if (i >= PG_NARGS())
+ INVALID_PARAMETER_ERROR("too few parameters");
+ arg_num = i++;
+ }
+
+ value = PG_GETARG_DATUM(arg_num);
+ isNull = PG_ARGISNULL(arg_num);
+ valtype = get_fn_expr_argtype(fcinfo->flinfo, arg_num);
+ getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+
+ switch (pd.field_type)
+ {
+ case 's':
+ {
+ if (!isNull)
+ {
+ char *outstr = OidOutputFunctionCall(typoutput, value);
+
+ appendStringInfoString(&str, outstr);
+ pfree(outstr);
+ }
+
+ break;
+ }
+
+ case 'I':
+ {
+ char *target_value;
+
+ if (isNull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("SQL identifier is undefined"),
+ errhint("You try to use NULL for %%I format")));
+
+ /* show it as sql identifier */
+ target_value = OidOutputFunctionCall(typoutput, value);
+ appendStringInfoString(&str, quote_identifier(target_value));
+ pfree(target_value);
+
+ break;
+ }
+
+ case 'L':
+ {
+ text *txt;
+ text *quoted_txt;
+
+ if (!isNull)
+ {
+ /* 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));
+
+ pfree(quoted_txt);
+ pfree(txt);
+ }
+ else
+ appendStringInfoString(&str, "NULL");
+
+ break;
+ }
+
+ default:
+ /* not possible */
+ elog(ERROR, "unexpected field type");
+ }
+ }
+ else
+ appendStringInfoChar(&str, cp[0]);
+ }
+
+ result = cstring_to_text_with_len(str.data, str.len);
+ pfree(str.data);
+
+ PG_RETURN_TEXT_P(result);
+ }
+
+ /*
+ * text_format_nv - nonvariadic wrapper for text_format function.
+ *
+ * note: this wrapper is necessary to be sanity_checks test ok
+ */
+ Datum
+ text_format_nv(PG_FUNCTION_ARGS)
+ {
+ return text_format(fcinfo);
+ }
*** ./src/include/catalog/pg_proc.h.orig 2010-10-11 08:33:15.000000000 +0200
--- ./src/include/catalog/pg_proc.h 2010-10-22 09:17:51.410199979 +0200
***************
*** 2741,2746 ****
--- 2741,2750 ----
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 = 3063 ( 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 = 3064 ( 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-10-11 08:33:15.000000000 +0200
--- ./src/include/utils/builtins.h 2010-10-22 08:49:28.642077134 +0200
***************
*** 742,747 ****
--- 742,749 ----
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);
*** ./src/test/regress/expected/text.out.orig 2010-10-11 08:33:15.000000000 +0200
--- ./src/test/regress/expected/text.out 2010-10-22 10:07:47.000000000 +0200
***************
*** 118,120 ****
--- 118,231 ----
5 | ahoj | ahoj
(11 rows)
+ select format(NULL);
+ format
+ --------
+
+ (1 row)
+
+ select format('Hello');
+ format
+ --------
+ Hello
+ (1 row)
+
+ select format('Hello %s', 'World');
+ format
+ -------------
+ Hello World
+ (1 row)
+
+ select format('Hello %%');
+ format
+ ---------
+ Hello %
+ (1 row)
+
+ select format('Hello %%%%');
+ format
+ ----------
+ Hello %%
+ (1 row)
+
+ -- should fail
+ select format('Hello %s %s', 'World');
+ ERROR: invalid formatting string for function format
+ DETAIL: too few parameters
+ select format('Hello %s');
+ ERROR: invalid formatting string for function format
+ DETAIL: too few parameters
+ select format('Hello %x', 20);
+ ERROR: invalid formatting string for function format
+ DETAIL: unsupported tag "%x"
+ -- check literal and sql identifiers
+ select format('INSERT INTO %I VALUES(%L,%L)', 'mytab', 10, 'Hello');
+ format
+ ----------------------------------------
+ INSERT INTO mytab VALUES('10','Hello')
+ (1 row)
+
+ select format('%s%s%s','Hello', NULL,'World');
+ format
+ ------------
+ HelloWorld
+ (1 row)
+
+ select format('INSERT INTO %I VALUES(%L,%L)', 'mytab', 10, NULL);
+ format
+ -------------------------------------
+ INSERT INTO mytab VALUES('10',NULL)
+ (1 row)
+
+ select format('INSERT INTO %I VALUES(%L,%L)', 'mytab', NULL, 'Hello');
+ format
+ ----------------------------------------
+ INSERT INTO mytab VALUES(NULL,'Hello')
+ (1 row)
+
+ -- should fail, sql identifier cannot be NULL
+ select format('INSERT INTO %I VALUES(%L,%L)', NULL, 10, 'Hello');
+ ERROR: SQL identifier is undefined
+ HINT: You try to use NULL for %I format
+ -- check positional placeholders
+ select format('%1$s %3$s', 1, 2, 3);
+ format
+ --------
+ 1 3
+ (1 row)
+
+ select format('%1$s %12$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
+ format
+ --------
+ 1 12
+ (1 row)
+
+ -- should fail
+ select format('%1$s %4$s', 1, 2, 3);
+ ERROR: invalid formatting string for function format
+ DETAIL: placeholder is out of range
+ select format('%1$s %13$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
+ ERROR: invalid formatting string for function format
+ DETAIL: placeholder is out of range
+ select format('%1s', 1);
+ ERROR: invalid formatting string for function format
+ DETAIL: expected '$'
+ select format('%1$', 1);
+ ERROR: invalid formatting string for function format
+ DETAIL: missing field type
+ select format('%1$1', 1);
+ ERROR: invalid formatting string for function format
+ DETAIL: positional parameter is defined already
+ --checkk mix of positional and ordered placeholders
+ select format('Hello %s %1$s %s', 'World', 'Hello again');
+ format
+ -------------------------------
+ Hello World World Hello again
+ (1 row)
+
+ select format('Hello %s %s, %2$s %2$s', 'World', 'Hello again');
+ format
+ --------------------------------------------------
+ Hello World Hello again, Hello again Hello again
+ (1 row)
+
*** ./src/test/regress/sql/text.sql.orig 2010-10-11 08:33:15.000000000 +0200
--- ./src/test/regress/sql/text.sql 2010-10-22 10:41:36.369200039 +0200
***************
*** 41,43 ****
--- 41,72 ----
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;
+
+ select format(NULL);
+ select format('Hello');
+ select format('Hello %s', 'World');
+ select format('Hello %%');
+ select format('Hello %%%%');
+ -- should fail
+ select format('Hello %s %s', 'World');
+ select format('Hello %s');
+ select format('Hello %x', 20);
+ -- check literal and sql identifiers
+ select format('INSERT INTO %I VALUES(%L,%L)', 'mytab', 10, 'Hello');
+ select format('%s%s%s','Hello', NULL,'World');
+ select format('INSERT INTO %I VALUES(%L,%L)', 'mytab', 10, NULL);
+ select format('INSERT INTO %I VALUES(%L,%L)', 'mytab', NULL, 'Hello');
+ -- should fail, sql identifier cannot be NULL
+ select format('INSERT INTO %I VALUES(%L,%L)', NULL, 10, 'Hello');
+ -- check positional placeholders
+ select format('%1$s %3$s', 1, 2, 3);
+ select format('%1$s %12$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
+ -- should fail
+ select format('%1$s %4$s', 1, 2, 3);
+ select format('%1$s %13$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
+ select format('%1s', 1);
+ select format('%1$', 1);
+ select format('%1$1', 1);
+ --checkk mix of positional and ordered placeholders
+ select format('Hello %s %1$s %s', 'World', 'Hello again');
+ select format('Hello %s %s, %2$s %2$s', 'World', 'Hello again');