*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 1532,1538 ****
SQL literal; %% outputs a literal %>.
A conversion can reference an explicit parameter position by preceding
the conversion specifier with n>$>, where
! n is the argument position. A warnig is raised
when positional and ordered placeholders are used together.
See also .
--- 1532,1540 ----
SQL literal; %% outputs a literal %>.
A conversion can reference an explicit parameter position by preceding
the conversion specifier with n>$>, where
! n is the argument position. Similary to C
! function sprintf>; is possible to specify a width for
! %s conversion. A warnig is raised
when positional and ordered placeholders are used together.
See also .
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
***************
*** 78,84 **** static bytea *bytea_overlay(bytea *t1, bytea *t2, int sp, int sl);
static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
static void text_format_string_conversion(StringInfo buf, char conversion,
FmgrInfo *typOutputInfo,
! Datum value, bool isNull);
static Datum text_to_array_internal(PG_FUNCTION_ARGS);
static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
const char *fldsep, const char *null_string);
--- 78,85 ----
static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
static void text_format_string_conversion(StringInfo buf, char conversion,
FmgrInfo *typOutputInfo,
! Datum value, bool isNull,
! bool use_width, int width);
static Datum text_to_array_internal(PG_FUNCTION_ARGS);
static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
const char *fldsep, const char *null_string);
***************
*** 3984,3989 **** text_reverse(PG_FUNCTION_ARGS)
--- 3985,4065 ----
}
/*
+ * Returns ptr of first non digit char. Raise error,
+ * when no digit is processed or when result of parsing is not number
+ */
+ static const char *
+ parse_digit_subformat(const char *start_ptr, const char *end_ptr, int *value)
+ {
+ const char *cp = start_ptr;
+ bool minus = false;
+ int inum = 0;
+
+ /* continue, only when start_ptr is less than end_ptr */
+ if (cp < end_ptr)
+ {
+ if (*cp == '-')
+ {
+ minus = true;
+ start_ptr = cp++;
+ }
+
+ while (cp < end_ptr)
+ {
+ if (*cp >= '0' && *cp <= '9')
+ {
+ int newnum = inum * 10 + (*cp - '0');
+
+ if (newnum / 10 != inum) /* overflow? */
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("number is out of range")));
+ inum = newnum;
+ ++cp;
+ }
+ else
+ break;
+ }
+
+ *value = minus ? - inum : inum;
+ }
+
+ /* digit cannot be last char */
+ if (cp >= end_ptr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unterminated conversion specifier")));
+
+ /* we call this routine, only when we expected number */
+ if (cp == start_ptr)
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("invalid format, missing expected number")));
+
+ /* argument number must by greather than zero */
+ if (*cp == '$')
+ {
+ if (minus)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("conversion specifies argument 0, but arguments are numbered from 1")));
+
+ if (inum == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("conversion specifies argument 0, but arguments are numbered from 1")));
+
+ /* $ must not be last char */
+ if (cp + 1 >= end_ptr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unterminated conversion specifier")));
+ }
+
+ return cp;
+ }
+
+ /*
* Returns a formated string
*/
Datum
***************
*** 4003,4009 **** text_format(PG_FUNCTION_ARGS)
Oid element_type = InvalidOid;
Oid prev_type = InvalidOid;
FmgrInfo typoutputfinfo;
! int position = 0;
bool positional = false;
bool ordered = false;
bool warning_emmited = false;
--- 4079,4085 ----
Oid element_type = InvalidOid;
Oid prev_type = InvalidOid;
FmgrInfo typoutputfinfo;
! int ordered_nargs = 0;
bool positional = false;
bool ordered = false;
bool warning_emmited = false;
***************
*** 4066,4071 **** text_format(PG_FUNCTION_ARGS)
--- 4142,4149 ----
Datum value;
bool isNull;
Oid typid;
+ int width = 0;
+ bool use_width = false;
/*
* If it's not the start of a conversion specifier, just copy it to
***************
*** 4090,4111 **** text_format(PG_FUNCTION_ARGS)
continue;
}
/*
! * If the user hasn't specified an argument position, we just advance
! * to the next one. If they have, we must parse it.
*/
! if (*cp < '0' || *cp > '9')
{
! /* don't allow mix styles - reflects glibc behave */
! if (positional && !warning_emmited)
{
! elog(WARNING, "ordered and positional placeholders are used together");
! warning_emmited = true;
}
! ordered = true;
! ++position;
! if (position <= 0) /* overflow? */
{
/*
* Should not happen, as you can't pass billions of arguments
--- 4168,4211 ----
continue;
}
+ arg = 0;
/*
! * User can specify a width or a position or both. The width should
! * be negative.
*/
! if (*cp == '-' || (*cp >= '0' && *cp <= '9'))
{
! cp = parse_digit_subformat(cp, end_ptr, &arg);
!
! if (*cp == '$')
{
! ++cp;
! if (*cp == '-' || (*cp >= '0' && *cp <= '9'))
! {
! cp = parse_digit_subformat(cp, end_ptr, &width);
!
! if (*cp == '$')
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
! errmsg("argument number is defined yet")));
!
! use_width = true;
! }
}
! else
! {
! /* previous digits was a width not argument number */
! width = arg;
! arg = 0;
! use_width = true;
! }
! }
! /* when argument not specified yet, use a next one */
! if (arg == 0)
! {
! ++ordered_nargs;
! if (ordered_nargs <= 0) /* overflow? */
{
/*
* Should not happen, as you can't pass billions of arguments
***************
*** 4115,4170 **** text_format(PG_FUNCTION_ARGS)
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("argument number is out of range")));
}
! arg = position;
! }
! else
! {
! bool unterminated = false;
!
! /* Parse digit string. */
! arg = 0;
! do
! {
! int newarg = arg * 10 + (*cp - '0');
! if (newarg / 10 != arg) /* overflow? */
! ereport(ERROR,
! (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
! errmsg("argument number is out of range")));
! arg = newarg;
! ++cp;
! } while (cp < end_ptr && *cp >= '0' && *cp <= '9');
!
! /*
! * If we ran off the end, or if there's not a $ next, or if the $
! * is the last character, the conversion specifier is improperly
! * terminated.
! */
! if (cp == end_ptr || *cp != '$')
! unterminated = true;
! else
{
! ++cp;
! if (cp == end_ptr)
! unterminated = true;
}
- if (unterminated)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("unterminated conversion specifier")));
if (ordered && !warning_emmited)
{
elog(WARNING, "ordered and positional placeholders are used together");
warning_emmited = true;
}
- positional = true;
! /* There's no argument 0. */
! if (arg == 0)
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
! errmsg("conversion specifies argument 0, but arguments are numbered from 1")));
}
/* Not enough arguments? Deduct 1 to avoid counting format string. */
--- 4215,4240 ----
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("argument number is out of range")));
}
! arg = ordered_nargs;
! /* raise warning when found mixed placeholders, but only once */
! if (positional && !warning_emmited)
{
! elog(WARNING, "ordered and positional placeholders are used together");
! warning_emmited = true;
}
+ ordered = true;
+ }
+ else
+ {
if (ordered && !warning_emmited)
{
elog(WARNING, "ordered and positional placeholders are used together");
warning_emmited = true;
}
! positional = true;
}
/* Not enough arguments? Deduct 1 to avoid counting format string. */
***************
*** 4212,4221 **** text_format(PG_FUNCTION_ARGS)
switch (*cp)
{
case 's':
case 'I':
case 'L':
text_format_string_conversion(&str, *cp, &typoutputfinfo,
! value, isNull);
break;
default:
ereport(ERROR,
--- 4282,4301 ----
switch (*cp)
{
case 's':
+ text_format_string_conversion(&str, *cp, &typoutputfinfo,
+ value, isNull,
+ use_width, width);
+ break;
+
case 'I':
case 'L':
+ if (use_width)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("conversion \"%c\" doesn't support the width specification", *cp)));
text_format_string_conversion(&str, *cp, &typoutputfinfo,
! value, isNull,
! false, 0);
break;
default:
ereport(ERROR,
***************
*** 4242,4248 **** text_format(PG_FUNCTION_ARGS)
static void
text_format_string_conversion(StringInfo buf, char conversion,
FmgrInfo *typOutputInfo,
! Datum value, bool isNull)
{
char *str;
--- 4322,4329 ----
static void
text_format_string_conversion(StringInfo buf, char conversion,
FmgrInfo *typOutputInfo,
! Datum value, bool isNull,
! bool use_width, int width)
{
char *str;
***************
*** 4276,4282 **** text_format_string_conversion(StringInfo buf, char conversion,
pfree(qstr);
}
else
! appendStringInfoString(buf, str);
/* Cleanup. */
pfree(str);
--- 4357,4386 ----
pfree(qstr);
}
else
! {
! /* fast path */
! if (!use_width)
! appendStringInfoString(buf, str);
! else
! {
! int len = pg_mbstrlen(str);
!
! if (width < 0)
! {
! /* allign to left */
! appendStringInfoString(buf, str);
! if (len < (-width))
! appendStringInfoSpaces(buf, - (len + width));
! }
! else
! {
! /* allign to right */
! if (len < width)
! appendStringInfoSpaces(buf, width - len);
! appendStringInfoString(buf, str);
! }
! }
! }
/* Cleanup. */
pfree(str);
*** a/src/test/regress/expected/text.out
--- b/src/test/regress/expected/text.out
***************
*** 258,269 **** select format('%1$s %4$s', 1, 2, 3);
ERROR: too few arguments for format
select format('%1$s %13$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
ERROR: too few arguments for format
- select format('%1s', 1);
- ERROR: unterminated conversion specifier
select format('%1$', 1);
ERROR: unterminated conversion specifier
select format('%1$1', 1);
! ERROR: unrecognized conversion specifier "1"
-- check mix of positional and ordered placeholders
-- should raise warning
select format('Hello %s %1$s %s', 'World', 'Hello again');
--- 258,267 ----
ERROR: too few arguments for format
select format('%1$s %13$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
ERROR: too few arguments for format
select format('%1$', 1);
ERROR: unterminated conversion specifier
select format('%1$1', 1);
! ERROR: unterminated conversion specifier
-- check mix of positional and ordered placeholders
-- should raise warning
select format('Hello %s %1$s %s', 'World', 'Hello again');
***************
*** 340,342 **** from generate_series(1,200) g(i);
--- 338,372 ----
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200
(1 row)
+ -- format with a width
+ select format('>>%10s<<', 'Hello');
+ format
+ ----------------
+ >> Hello<<
+ (1 row)
+
+ select format('>>%-10s<<', 'Hello');
+ format
+ ----------------
+ >>Hello <<
+ (1 row)
+
+ select format('>>%1$10s<<', 'Hello');
+ format
+ ----------------
+ >> Hello<<
+ (1 row)
+
+ select format('>>%1$-10s<<', 'Hello');
+ format
+ ----------------
+ >>Hello <<
+ (1 row)
+
+ -- should fail
+ select format('>>%1$1$s<<', 'Hello');
+ ERROR: argument number is defined yet
+ select format('>>%10I<<', 'tablename');
+ ERROR: conversion "I" doesn't support the width specification
+ select format('>>%1$10L<<', 'tablename');
+ ERROR: conversion "L" doesn't support the width specification
*** a/src/test/regress/sql/text.sql
--- b/src/test/regress/sql/text.sql
***************
*** 78,84 **** 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);
-- check mix of positional and ordered placeholders
--- 78,83 ----
***************
*** 99,101 **** select format('Hello', variadic NULL::int[]);
--- 98,112 ----
-- variadic argument allows simulating more than FUNC_MAX_ARGS parameters
select format(string_agg('%s',','), variadic array_agg(i))
from generate_series(1,200) g(i);
+
+
+ -- format with a width
+ select format('>>%10s<<', 'Hello');
+ select format('>>%-10s<<', 'Hello');
+ select format('>>%1$10s<<', 'Hello');
+ select format('>>%1$-10s<<', 'Hello');
+
+ -- should fail
+ select format('>>%1$1$s<<', 'Hello');
+ select format('>>%10I<<', 'tablename');
+ select format('>>%1$10L<<', 'tablename');