format_with_width.diff
application/octet-stream
Filename: format_with_width.diff
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: context
| File | + | − |
|---|---|---|
| src/backend/utils/adt/varlena.c | 154 | 0 |
| src/test/regress/expected/text.out | 22 | 0 |
| src/test/regress/sql/text.sql | 8 | 0 |
*** 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 next char after digit. Raise error,
+ * when no digit is processed or when parsed values are not valid
+ */
+ 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 number")));
+
+ /* argument number must by greather than zero */
+ if (*cp == '$')
+ {
+ if (minus)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("conversion specifies argument less 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
***************
*** 3958,3964 **** text_format(PG_FUNCTION_ARGS)
const char *start_ptr;
const char *end_ptr;
text *result;
! int arg = 0;
/* When format string is null, returns null */
if (PG_ARGISNULL(0))
--- 4034,4040 ----
const char *start_ptr;
const char *end_ptr;
text *result;
! int args = 0;
/* When format string is null, returns null */
if (PG_ARGISNULL(0))
***************
*** 3976,3981 **** text_format(PG_FUNCTION_ARGS)
--- 4052,4060 ----
Datum value;
bool isNull;
Oid typid;
+ bool with_width;
+ int arg;
+ int width;
/*
* If it's not the start of a conversion specifier, just copy it to
***************
*** 4000,4013 **** 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')
{
! ++arg;
! if (arg <= 0) /* overflow? */
{
/*
* Should not happen, as you can't pass billions of arguments
--- 4079,4124 ----
continue;
}
! if (cp >= end_ptr)
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
! errmsg("unterminated conversion specifier")));
!
! arg = 0;
! with_width = false;
!
! 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;
! }
! }
!
! if (arg == 0)
! {
! ++args;
! if (args <= 0) /* overflow? */
{
/*
* Should not happen, as you can't pass billions of arguments
***************
*** 4017,4064 **** text_format(PG_FUNCTION_ARGS)
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("argument number is out of range")));
}
- }
- 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")));
! /* 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. */
--- 4128,4135 ----
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("argument number is out of range")));
}
! arg = args;
}
/* Not enough arguments? Deduct 1 to avoid counting format string. */
***************
*** 4081,4087 **** text_format(PG_FUNCTION_ARGS)
case 's':
case 'I':
case 'L':
! text_format_string_conversion(&str, *cp, typid, value, isNull);
break;
default:
ereport(ERROR,
--- 4152,4159 ----
case 's':
case 'I':
case 'L':
! text_format_string_conversion(&str, *cp, typid, value, isNull,
! with_width, width);
break;
default:
ereport(ERROR,
***************
*** 4101,4112 **** 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;
char *str;
/* Handle NULL arguments before trying to stringify the value. */
if (isNull)
{
--- 4173,4190 ----
/* 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;
char *str;
+ if (conversion != 's' && with_width)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("width's specification is available only for %%s specifier")));
+
/* Handle NULL arguments before trying to stringify the value. */
if (isNull)
{
***************
*** 4138,4144 **** text_format_string_conversion(StringInfo buf, char conversion,
pfree(qstr);
}
else
! appendStringInfoString(buf, str);
/* Cleanup. */
pfree(str);
--- 4216,4245 ----
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
***************
*** 223,233 **** 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
select format('Hello %s %1$s %s', 'World', 'Hello again');
format
--- 223,237 ----
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);
! format
! --------
! 1
! (1 row)
!
select format('%1$', 1);
ERROR: unterminated conversion specifier
select format('%1$1', 1);
! ERROR: unterminated conversion specifier
--checkk mix of positional and ordered placeholders
select format('Hello %s %1$s %s', 'World', 'Hello again');
format
***************
*** 241,243 **** select format('Hello %s %s, %2$s %2$s', 'World', 'Hello again');
--- 245,263 ----
Hello World Hello again, Hello again Hello again
(1 row)
+ select format('%s %2$s %s', 'Hello', 'World');
+ format
+ -------------------
+ Hello World World
+ (1 row)
+
+ --check width
+ select format('||%4s|| ||%-4s||', 'ab', 'ab');
+ format
+ -------------------
+ || ab|| ||ab ||
+ (1 row)
+
+ --should fail
+ select format('INSERT INTO %10I VALUES(%L,%L)', 'mytab', 10, 'Hello');
+ ERROR: width's specification is available only for %s specifier
*** a/src/test/regress/sql/text.sql
--- b/src/test/regress/sql/text.sql
***************
*** 76,78 **** select format('%1$1', 1);
--- 76,86 ----
--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');
+
+ select format('%s %2$s %s', 'Hello', 'World');
+
+ --check width
+ select format('||%4s|| ||%-4s||', 'ab', 'ab');
+
+ --should fail
+ select format('INSERT INTO %10I VALUES(%L,%L)', 'mytab', 10, 'Hello');