*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 1530,1536 ****
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 .
--- 1530,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. 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
***************
*** 77,83 **** static bytea *bytea_substring(Datum str,
static bytea *bytea_overlay(bytea *t1, bytea *t2, int sp, int sl);
static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
void text_format_string_conversion(StringInfo buf, char conversion,
! Oid typid, Datum value, bool isNull);
static Datum text_to_array_internal(PG_FUNCTION_ARGS);
static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
--- 77,84 ----
static bytea *bytea_overlay(bytea *t1, bytea *t2, int sp, int sl);
static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
void text_format_string_conversion(StringInfo buf, char conversion,
! Oid typid, Datum value, bool isNull,
! bool with_width, int width);
static Datum text_to_array_internal(PG_FUNCTION_ARGS);
static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
***************
*** 3947,3952 **** text_reverse(PG_FUNCTION_ARGS)
--- 3948,4028 ----
}
/*
+ * 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
***************
*** 3959,3965 **** text_format(PG_FUNCTION_ARGS)
const char *end_ptr;
text *result;
int arg;
! int position = 0;
bool positional = false;
bool ordered = false;
bool warning_emmited = false;
--- 4035,4041 ----
const char *end_ptr;
text *result;
int arg;
! int ordered_nargs = 0;
bool positional = false;
bool ordered = false;
bool warning_emmited = false;
***************
*** 3980,3985 **** text_format(PG_FUNCTION_ARGS)
--- 4056,4063 ----
Datum value;
bool isNull;
Oid typid;
+ int width = 0;
+ bool with_width = false;
/*
* If it's not the start of a conversion specifier, just copy it to
***************
*** 4004,4025 **** 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
--- 4082,4125 ----
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")));
!
! with_width = true;
! }
}
! else
! {
! /* previous digits was a width not argument number */
! width = arg;
! arg = 0;
! with_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
***************
*** 4029,4084 **** 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. */
--- 4129,4154 ----
(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. */
***************
*** 4099,4108 **** text_format(PG_FUNCTION_ARGS)
switch (*cp)
{
case 's':
case 'I':
case 'L':
! text_format_string_conversion(&str, *cp, typid, value, isNull);
break;
default:
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
--- 4169,4186 ----
switch (*cp)
{
case 's':
+ text_format_string_conversion(&str, *cp, typid, value, isNull, with_width, width);
+ break;
+
case 'I':
case 'L':
! if (with_width)
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
! errmsg("conversion \"%c\" doesn't suppport the width", *cp)));
! text_format_string_conversion(&str, *cp, typid, value, isNull, false, 0);
break;
+
default:
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
***************
*** 4121,4127 **** text_format(PG_FUNCTION_ARGS)
/* Format a %s, %I, or %L conversion. */
void
text_format_string_conversion(StringInfo buf, char conversion,
! Oid typid, Datum value, bool isNull)
{
Oid typOutput;
bool typIsVarlena;
--- 4199,4206 ----
/* Format a %s, %I, or %L conversion. */
void
text_format_string_conversion(StringInfo buf, char conversion,
! Oid typid, Datum value, bool isNull,
! bool with_width, int width)
{
Oid typOutput;
bool typIsVarlena;
***************
*** 4158,4164 **** text_format_string_conversion(StringInfo buf, char conversion,
pfree(qstr);
}
else
! appendStringInfoString(buf, str);
/* Cleanup. */
pfree(str);
--- 4237,4266 ----
pfree(qstr);
}
else
! {
! /* fast path */
! if (!with_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
***************
*** 222,233 **** 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"
--checkk mix of positional and ordered placeholders
--should raise warnings
select format('Hello %s %1$s %s', 'World', 'Hello again');
--- 222,231 ----
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
--checkk mix of positional and ordered placeholders
--should raise warnings
select format('Hello %s %1$s %s', 'World', 'Hello again');
***************
*** 251,253 **** WARNING: ordered and positional placeholders are used together
--- 249,283 ----
Hello World Hello again Hello again
(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 suppport the width
+ select format('>>%1$10L<<', 'tablename');
+ ERROR: conversion "L" doesn't suppport the width
*** a/src/test/regress/sql/text.sql
--- b/src/test/regress/sql/text.sql
***************
*** 70,76 **** 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
--- 70,75 ----
***************
*** 78,80 **** select format('%1$1', 1);
--- 77,90 ----
select format('Hello %s %1$s %s', 'World', 'Hello again');
select format('Hello %s %s, %2$s %2$s', 'World', 'Hello again');
select format('Hello %s %2$s %s', 'World', 'Hello again');
+
+ -- 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');