mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-01-12 18:34:36 +08:00
Extend format() to handle field width and left/right alignment.
This change adds some more standard sprintf() functionality to format(). Pavel Stehule, reviewed by Dean Rasheed and Kyotaro Horiguchi
This commit is contained in:
parent
1a1832eb08
commit
73e7025bd8
@ -1519,21 +1519,13 @@
|
|||||||
<primary>format</primary>
|
<primary>format</primary>
|
||||||
</indexterm>
|
</indexterm>
|
||||||
<literal><function>format</function>(<parameter>formatstr</parameter> <type>text</type>
|
<literal><function>format</function>(<parameter>formatstr</parameter> <type>text</type>
|
||||||
[, <parameter>str</parameter> <type>"any"</type> [, ...] ])</literal>
|
[, <parameter>formatarg</parameter> <type>"any"</type> [, ...] ])</literal>
|
||||||
</entry>
|
</entry>
|
||||||
<entry><type>text</type></entry>
|
<entry><type>text</type></entry>
|
||||||
<entry>
|
<entry>
|
||||||
Format arguments according to a format string.
|
Format arguments according to a format string.
|
||||||
This function is similar to the C function
|
This function is similar to the C function <function>sprintf</>.
|
||||||
<function>sprintf</>, but only the following conversion specifications
|
See <xref linkend="functions-string-format">.
|
||||||
are recognized: <literal>%s</literal> interpolates the corresponding
|
|
||||||
argument as a string; <literal>%I</literal> escapes its argument as
|
|
||||||
an SQL identifier; <literal>%L</literal> escapes its argument as an
|
|
||||||
SQL literal; <literal>%%</literal> outputs a literal <literal>%</>.
|
|
||||||
A conversion can reference an explicit parameter position by preceding
|
|
||||||
the conversion specifier with <literal><replaceable>n</>$</>, where
|
|
||||||
<replaceable>n</replaceable> is the argument position.
|
|
||||||
See also <xref linkend="plpgsql-quote-literal-example">.
|
|
||||||
</entry>
|
</entry>
|
||||||
<entry><literal>format('Hello %s, %1$s', 'World')</literal></entry>
|
<entry><literal>format('Hello %s, %1$s', 'World')</literal></entry>
|
||||||
<entry><literal>Hello World, World</literal></entry>
|
<entry><literal>Hello World, World</literal></entry>
|
||||||
@ -2847,6 +2839,214 @@
|
|||||||
</tgroup>
|
</tgroup>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<sect2 id="functions-string-format">
|
||||||
|
<title><function>format</function></title>
|
||||||
|
|
||||||
|
<indexterm>
|
||||||
|
<primary>format</primary>
|
||||||
|
</indexterm>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The function <function>format</> produces output formatted according to
|
||||||
|
a format string, in a style similar to the C function
|
||||||
|
<function>sprintf</>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<synopsis>
|
||||||
|
<function>format</>(<parameter>formatstr</> <type>text</> [, <parameter>formatarg</> <type>"any"</> [, ...] ])
|
||||||
|
</synopsis>
|
||||||
|
<replaceable>formatstr</> is a format string that specifies how the
|
||||||
|
result should be formatted. Text in the format string is copied
|
||||||
|
directly to the result, except where <firstterm>format specifiers</> are
|
||||||
|
used. Format specifiers act as placeholders in the string, defining how
|
||||||
|
subsequent function arguments should be formatted and inserted into the
|
||||||
|
result. Each <replaceable>formatarg</> argument is converted to text
|
||||||
|
according to the usual output rules for its data type, and then formatted
|
||||||
|
and inserted into the result string according to the format specifier(s).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Format specifiers are introduced by a <literal>%</> character and have
|
||||||
|
the form
|
||||||
|
<synopsis>
|
||||||
|
%[<replaceable>position</>][<replaceable>flags</>][<replaceable>width</>]<replaceable>type</>
|
||||||
|
</synopsis>
|
||||||
|
where the component fields are:
|
||||||
|
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term><replaceable>position</replaceable> (optional)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
A string of the form <literal><replaceable>n</>$</> where
|
||||||
|
<replaceable>n</> is the index of the argument to print.
|
||||||
|
Index 1 means the first argument after
|
||||||
|
<replaceable>formatstr</>. If the <replaceable>position</> is
|
||||||
|
omitted, the default is to use the next argument in sequence.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><replaceable>flags</replaceable> (optional)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Additional options controlling how the format specifier's output is
|
||||||
|
formatted. Currently the only supported flag is a minus sign
|
||||||
|
(<literal>-</>) which will cause the format specifier's output to be
|
||||||
|
left-justified. This has no effect unless the <replaceable>width</>
|
||||||
|
field is also specified.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><replaceable>width</replaceable> (optional)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Specifies the <emphasis>minimum</> number of characters to use to
|
||||||
|
display the format specifier's output. The output is padded on the
|
||||||
|
left or right (depending on the <literal>-</> flag) with spaces as
|
||||||
|
needed to fill the width. A too-small width does not cause
|
||||||
|
truncation of the output, but is simply ignored. The width may be
|
||||||
|
specified using any of the following: a positive integer; an
|
||||||
|
asterisk (<literal>*</>) to use the next function argument as the
|
||||||
|
width; or a string of the form <literal>*<replaceable>n</>$</> to
|
||||||
|
use the <replaceable>n</>th function argument as the width.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
If the width comes from a function argument, that argument is
|
||||||
|
consumed before the argument that is used for the format specifier's
|
||||||
|
value. If the width argument is negative, the result is left
|
||||||
|
aligned (as if the <literal>-</> flag had been specified) within a
|
||||||
|
field of length <function>abs</>(<replaceable>width</replaceable>).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><replaceable>type</replaceable> (required)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The type of format conversion to use to produce the format
|
||||||
|
specifier's output. The following types are supported:
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>s</literal> formats the argument value as a simple
|
||||||
|
string. A null value is treated as an empty string.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>I</literal> treats the argument value as an SQL
|
||||||
|
identifier, double-quoting it if necessary.
|
||||||
|
It is an error for the value to be null.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>L</literal> quotes the argument value as an SQL literal.
|
||||||
|
A null value is displayed as the string <literal>NULL</>, without
|
||||||
|
quotes.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
In addition to the format specifiers described above, the special sequence
|
||||||
|
<literal>%%</> may be used to output a literal <literal>%</> character.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Here are some examples of the basic format conversions:
|
||||||
|
|
||||||
|
<screen>
|
||||||
|
SELECT format('Hello %s', 'World');
|
||||||
|
<lineannotation>Result: </lineannotation><computeroutput>Hello World</computeroutput>
|
||||||
|
|
||||||
|
SELECT format('Testing %s, %s, %s, %%', 'one', 'two', 'three');
|
||||||
|
<lineannotation>Result: </><computeroutput>Testing one, two, three, %</>
|
||||||
|
|
||||||
|
SELECT format('INSERT INTO %I VALUES(%L)', 'Foo bar', E'O\'Reilly');
|
||||||
|
<lineannotation>Result: </lineannotation><computeroutput>INSERT INTO "Foo bar" VALUES('O''Reilly')</computeroutput>
|
||||||
|
|
||||||
|
SELECT format('INSERT INTO %I VALUES(%L)', 'locations', E'C:\\Program Files');
|
||||||
|
<lineannotation>Result: </lineannotation><computeroutput>INSERT INTO locations VALUES(E'C:\\Program Files')</computeroutput>
|
||||||
|
</screen>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Here are examples using <replaceable>width</replaceable> fields
|
||||||
|
and the <literal>-</> flag:
|
||||||
|
|
||||||
|
<screen>
|
||||||
|
SELECT format('|%10s|', 'foo');
|
||||||
|
<lineannotation>Result: </><computeroutput>| foo|</>
|
||||||
|
|
||||||
|
SELECT format('|%-10s|', 'foo');
|
||||||
|
<lineannotation>Result: </><computeroutput>|foo |</>
|
||||||
|
|
||||||
|
SELECT format('|%*s|', 10, 'foo');
|
||||||
|
<lineannotation>Result: </><computeroutput>| foo|</>
|
||||||
|
|
||||||
|
SELECT format('|%*s|', -10, 'foo');
|
||||||
|
<lineannotation>Result: </><computeroutput>|foo |</>
|
||||||
|
|
||||||
|
SELECT format('|%-*s|', 10, 'foo');
|
||||||
|
<lineannotation>Result: </><computeroutput>|foo |</>
|
||||||
|
|
||||||
|
SELECT format('|%-*s|', -10, 'foo');
|
||||||
|
<lineannotation>Result: </><computeroutput>|foo |</>
|
||||||
|
</screen>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
These examples show use of <replaceable>position</> fields:
|
||||||
|
|
||||||
|
<screen>
|
||||||
|
SELECT format('Testing %3$s, %2$s, %1$s', 'one', 'two', 'three');
|
||||||
|
<lineannotation>Result: </><computeroutput>Testing three, two, one</>
|
||||||
|
|
||||||
|
SELECT format('|%*2$s|', 'foo', 10, 'bar');
|
||||||
|
<lineannotation>Result: </><computeroutput>| bar|</>
|
||||||
|
|
||||||
|
SELECT format('|%1$*2$s|', 'foo', 10, 'bar');
|
||||||
|
<lineannotation>Result: </><computeroutput>| foo|</>
|
||||||
|
</screen>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Unlike the standard C function <function>sprintf</>,
|
||||||
|
<productname>PostgreSQL</>'s <function>format</> function allows format
|
||||||
|
specifiers with and without <replaceable>position</> fields to be mixed
|
||||||
|
in the same format string. A format specifier without a
|
||||||
|
<replaceable>position</> field always uses the next argument after the
|
||||||
|
last argument consumed.
|
||||||
|
In addition, the <function>format</> function does not require all
|
||||||
|
function arguments to be used in the format string.
|
||||||
|
For example:
|
||||||
|
|
||||||
|
<screen>
|
||||||
|
SELECT format('Testing %3$s, %2$s, %s', 'one', 'two', 'three');
|
||||||
|
<lineannotation>Result: </><computeroutput>Testing three, two, three</>
|
||||||
|
</screen>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <literal>%I</> and <literal>%L</> format specifiers are particularly
|
||||||
|
useful for safely constructing dynamic SQL statements. See
|
||||||
|
<xref linkend="plpgsql-quote-literal-example">.
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
</sect1>
|
</sect1>
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,32 +56,41 @@ typedef struct
|
|||||||
#define PG_GETARG_UNKNOWN_P_COPY(n) DatumGetUnknownPCopy(PG_GETARG_DATUM(n))
|
#define PG_GETARG_UNKNOWN_P_COPY(n) DatumGetUnknownPCopy(PG_GETARG_DATUM(n))
|
||||||
#define PG_RETURN_UNKNOWN_P(x) PG_RETURN_POINTER(x)
|
#define PG_RETURN_UNKNOWN_P(x) PG_RETURN_POINTER(x)
|
||||||
|
|
||||||
static int text_cmp(text *arg1, text *arg2, Oid collid);
|
|
||||||
static int32 text_length(Datum str);
|
static int32 text_length(Datum str);
|
||||||
static int text_position(text *t1, text *t2);
|
|
||||||
static void text_position_setup(text *t1, text *t2, TextPositionState *state);
|
|
||||||
static int text_position_next(int start_pos, TextPositionState *state);
|
|
||||||
static void text_position_cleanup(TextPositionState *state);
|
|
||||||
static text *text_catenate(text *t1, text *t2);
|
static text *text_catenate(text *t1, text *t2);
|
||||||
static text *text_substring(Datum str,
|
static text *text_substring(Datum str,
|
||||||
int32 start,
|
int32 start,
|
||||||
int32 length,
|
int32 length,
|
||||||
bool length_not_specified);
|
bool length_not_specified);
|
||||||
static text *text_overlay(text *t1, text *t2, int sp, int sl);
|
static text *text_overlay(text *t1, text *t2, int sp, int sl);
|
||||||
static void appendStringInfoText(StringInfo str, const text *t);
|
static int text_position(text *t1, text *t2);
|
||||||
|
static void text_position_setup(text *t1, text *t2, TextPositionState *state);
|
||||||
|
static int text_position_next(int start_pos, TextPositionState *state);
|
||||||
|
static void text_position_cleanup(TextPositionState *state);
|
||||||
|
static int text_cmp(text *arg1, text *arg2, Oid collid);
|
||||||
static bytea *bytea_catenate(bytea *t1, bytea *t2);
|
static bytea *bytea_catenate(bytea *t1, bytea *t2);
|
||||||
static bytea *bytea_substring(Datum str,
|
static bytea *bytea_substring(Datum str,
|
||||||
int S,
|
int S,
|
||||||
int L,
|
int L,
|
||||||
bool length_not_specified);
|
bool length_not_specified);
|
||||||
static bytea *bytea_overlay(bytea *t1, bytea *t2, int sp, int sl);
|
static bytea *bytea_overlay(bytea *t1, bytea *t2, int sp, int sl);
|
||||||
static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
|
static void appendStringInfoText(StringInfo str, const text *t);
|
||||||
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 Datum text_to_array_internal(PG_FUNCTION_ARGS);
|
||||||
static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
|
static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
|
||||||
const char *fldsep, const char *null_string);
|
const char *fldsep, const char *null_string);
|
||||||
|
static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
|
||||||
|
static bool text_format_parse_digits(const char **ptr, const char *end_ptr,
|
||||||
|
int *value);
|
||||||
|
static const char *text_format_parse_format(const char *start_ptr,
|
||||||
|
const char *end_ptr,
|
||||||
|
int *argpos, int *widthpos,
|
||||||
|
int *flags, int *width);
|
||||||
|
static void text_format_string_conversion(StringInfo buf, char conversion,
|
||||||
|
FmgrInfo *typOutputInfo,
|
||||||
|
Datum value, bool isNull,
|
||||||
|
int flags, int width);
|
||||||
|
static void text_format_append_string(StringInfo buf, const char *str,
|
||||||
|
int flags, int width);
|
||||||
|
|
||||||
|
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
@ -3996,8 +4005,22 @@ text_reverse(PG_FUNCTION_ARGS)
|
|||||||
PG_RETURN_TEXT_P(result);
|
PG_RETURN_TEXT_P(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns a formated string
|
* Support macros for text_format()
|
||||||
|
*/
|
||||||
|
#define TEXT_FORMAT_FLAG_MINUS 0x0001 /* is minus flag present? */
|
||||||
|
|
||||||
|
#define ADVANCE_PARSE_POINTER(ptr,end_ptr) \
|
||||||
|
do { \
|
||||||
|
if (++(ptr) >= (end_ptr)) \
|
||||||
|
ereport(ERROR, \
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), \
|
||||||
|
errmsg("unterminated format specifier"))); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a formatted string
|
||||||
*/
|
*/
|
||||||
Datum
|
Datum
|
||||||
text_format(PG_FUNCTION_ARGS)
|
text_format(PG_FUNCTION_ARGS)
|
||||||
@ -4008,16 +4031,18 @@ text_format(PG_FUNCTION_ARGS)
|
|||||||
const char *start_ptr;
|
const char *start_ptr;
|
||||||
const char *end_ptr;
|
const char *end_ptr;
|
||||||
text *result;
|
text *result;
|
||||||
int arg = 0;
|
int arg;
|
||||||
bool funcvariadic;
|
bool funcvariadic;
|
||||||
int nargs;
|
int nargs;
|
||||||
Datum *elements = NULL;
|
Datum *elements = NULL;
|
||||||
bool *nulls = NULL;
|
bool *nulls = NULL;
|
||||||
Oid element_type = InvalidOid;
|
Oid element_type = InvalidOid;
|
||||||
Oid prev_type = InvalidOid;
|
Oid prev_type = InvalidOid;
|
||||||
|
Oid prev_width_type = InvalidOid;
|
||||||
FmgrInfo typoutputfinfo;
|
FmgrInfo typoutputfinfo;
|
||||||
|
FmgrInfo typoutputinfo_width;
|
||||||
|
|
||||||
/* When format string is null, returns null */
|
/* When format string is null, immediately return null */
|
||||||
if (PG_ARGISNULL(0))
|
if (PG_ARGISNULL(0))
|
||||||
PG_RETURN_NULL();
|
PG_RETURN_NULL();
|
||||||
|
|
||||||
@ -4081,10 +4106,15 @@ text_format(PG_FUNCTION_ARGS)
|
|||||||
start_ptr = VARDATA_ANY(fmt);
|
start_ptr = VARDATA_ANY(fmt);
|
||||||
end_ptr = start_ptr + VARSIZE_ANY_EXHDR(fmt);
|
end_ptr = start_ptr + VARSIZE_ANY_EXHDR(fmt);
|
||||||
initStringInfo(&str);
|
initStringInfo(&str);
|
||||||
|
arg = 1; /* next argument position to print */
|
||||||
|
|
||||||
/* Scan format string, looking for conversion specifiers. */
|
/* Scan format string, looking for conversion specifiers. */
|
||||||
for (cp = start_ptr; cp < end_ptr; cp++)
|
for (cp = start_ptr; cp < end_ptr; cp++)
|
||||||
{
|
{
|
||||||
|
int argpos;
|
||||||
|
int widthpos;
|
||||||
|
int flags;
|
||||||
|
int width;
|
||||||
Datum value;
|
Datum value;
|
||||||
bool isNull;
|
bool isNull;
|
||||||
Oid typid;
|
Oid typid;
|
||||||
@ -4099,11 +4129,7 @@ text_format(PG_FUNCTION_ARGS)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Did we run off the end of the string? */
|
ADVANCE_PARSE_POINTER(cp, end_ptr);
|
||||||
if (++cp >= end_ptr)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
||||||
errmsg("unterminated conversion specifier")));
|
|
||||||
|
|
||||||
/* Easy case: %% outputs a single % */
|
/* Easy case: %% outputs a single % */
|
||||||
if (*cp == '%')
|
if (*cp == '%')
|
||||||
@ -4112,69 +4138,89 @@ text_format(PG_FUNCTION_ARGS)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Parse the optional portions of the format specifier */
|
||||||
|
cp = text_format_parse_format(cp, end_ptr,
|
||||||
|
&argpos, &widthpos,
|
||||||
|
&flags, &width);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the user hasn't specified an argument position, we just advance
|
* Next we should see the main conversion specifier. Whether or not
|
||||||
* to the next one. If they have, we must parse it.
|
* an argument position was present, it's known that at least one
|
||||||
|
* character remains in the string at this point. Experience suggests
|
||||||
|
* that it's worth checking that that character is one of the expected
|
||||||
|
* ones before we try to fetch arguments, so as to produce the least
|
||||||
|
* confusing response to a mis-formatted specifier.
|
||||||
*/
|
*/
|
||||||
if (*cp < '0' || *cp > '9')
|
if (strchr("sIL", *cp) == NULL)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
|
errmsg("unrecognized conversion type specifier \"%c\"",
|
||||||
|
*cp)));
|
||||||
|
|
||||||
|
/* If indirect width was specified, get its value */
|
||||||
|
if (widthpos >= 0)
|
||||||
{
|
{
|
||||||
++arg;
|
/* Collect the specified or next argument position */
|
||||||
if (arg <= 0) /* overflow? */
|
if (widthpos > 0)
|
||||||
{
|
arg = widthpos;
|
||||||
/*
|
if (arg >= nargs)
|
||||||
* Should not happen, as you can't pass billions of arguments
|
|
||||||
* to a function, but better safe than sorry.
|
|
||||||
*/
|
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
errmsg("argument number is out of range")));
|
errmsg("too few arguments for format")));
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bool unterminated = false;
|
|
||||||
|
|
||||||
/* Parse digit string. */
|
/* Get the value and type of the selected argument */
|
||||||
arg = 0;
|
if (!funcvariadic)
|
||||||
do
|
|
||||||
{
|
{
|
||||||
int newarg = arg * 10 + (*cp - '0');
|
value = PG_GETARG_DATUM(arg);
|
||||||
|
isNull = PG_ARGISNULL(arg);
|
||||||
if (newarg / 10 != arg) /* overflow? */
|
typid = get_fn_expr_argtype(fcinfo->flinfo, arg);
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
++cp;
|
value = elements[arg - 1];
|
||||||
if (cp == end_ptr)
|
isNull = nulls[arg - 1];
|
||||||
unterminated = true;
|
typid = element_type;
|
||||||
}
|
}
|
||||||
if (unterminated)
|
if (!OidIsValid(typid))
|
||||||
ereport(ERROR,
|
elog(ERROR, "could not determine data type of format() input");
|
||||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
||||||
errmsg("unterminated conversion specifier")));
|
|
||||||
|
|
||||||
/* There's no argument 0. */
|
arg++;
|
||||||
if (arg == 0)
|
|
||||||
ereport(ERROR,
|
/* We can treat NULL width the same as zero */
|
||||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
if (isNull)
|
||||||
errmsg("conversion specifies argument 0, but arguments are numbered from 1")));
|
width = 0;
|
||||||
|
else if (typid == INT4OID)
|
||||||
|
width = DatumGetInt32(value);
|
||||||
|
else if (typid == INT2OID)
|
||||||
|
width = DatumGetInt16(value);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* For less-usual datatypes, convert to text then to int */
|
||||||
|
char *str;
|
||||||
|
|
||||||
|
if (typid != prev_width_type)
|
||||||
|
{
|
||||||
|
Oid typoutputfunc;
|
||||||
|
bool typIsVarlena;
|
||||||
|
|
||||||
|
getTypeOutputInfo(typid, &typoutputfunc, &typIsVarlena);
|
||||||
|
fmgr_info(typoutputfunc, &typoutputinfo_width);
|
||||||
|
prev_width_type = typid;
|
||||||
|
}
|
||||||
|
|
||||||
|
str = OutputFunctionCall(&typoutputinfo_width, value);
|
||||||
|
|
||||||
|
/* pg_atoi will complain about bad data or overflow */
|
||||||
|
width = pg_atoi(str, sizeof(int), '\0');
|
||||||
|
|
||||||
|
pfree(str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Not enough arguments? Deduct 1 to avoid counting format string. */
|
/* Collect the specified or next argument position */
|
||||||
if (arg > nargs - 1)
|
if (argpos > 0)
|
||||||
|
arg = argpos;
|
||||||
|
if (arg >= nargs)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
errmsg("too few arguments for format")));
|
errmsg("too few arguments for format")));
|
||||||
@ -4195,6 +4241,8 @@ text_format(PG_FUNCTION_ARGS)
|
|||||||
if (!OidIsValid(typid))
|
if (!OidIsValid(typid))
|
||||||
elog(ERROR, "could not determine data type of format() input");
|
elog(ERROR, "could not determine data type of format() input");
|
||||||
|
|
||||||
|
arg++;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the appropriate typOutput function, reusing previous one if
|
* Get the appropriate typOutput function, reusing previous one if
|
||||||
* same type as previous argument. That's particularly useful in the
|
* same type as previous argument. That's particularly useful in the
|
||||||
@ -4211,9 +4259,7 @@ text_format(PG_FUNCTION_ARGS)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* At this point, we should see the main conversion specifier. Whether
|
* And now we can format the value.
|
||||||
* or not an argument position was present, it's known that at least
|
|
||||||
* one character remains in the string at this point.
|
|
||||||
*/
|
*/
|
||||||
switch (*cp)
|
switch (*cp)
|
||||||
{
|
{
|
||||||
@ -4221,13 +4267,16 @@ text_format(PG_FUNCTION_ARGS)
|
|||||||
case 'I':
|
case 'I':
|
||||||
case 'L':
|
case 'L':
|
||||||
text_format_string_conversion(&str, *cp, &typoutputfinfo,
|
text_format_string_conversion(&str, *cp, &typoutputfinfo,
|
||||||
value, isNull);
|
value, isNull,
|
||||||
|
flags, width);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
/* should not get here, because of previous check */
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
errmsg("unrecognized conversion specifier \"%c\"",
|
errmsg("unrecognized conversion type specifier \"%c\"",
|
||||||
*cp)));
|
*cp)));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4244,19 +4293,157 @@ text_format(PG_FUNCTION_ARGS)
|
|||||||
PG_RETURN_TEXT_P(result);
|
PG_RETURN_TEXT_P(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Format a %s, %I, or %L conversion. */
|
/*
|
||||||
|
* Parse contiguous digits as a decimal number.
|
||||||
|
*
|
||||||
|
* Returns true if some digits could be parsed.
|
||||||
|
* The value is returned into *value, and *ptr is advanced to the next
|
||||||
|
* character to be parsed.
|
||||||
|
*
|
||||||
|
* Note parsing invariant: at least one character is known available before
|
||||||
|
* string end (end_ptr) at entry, and this is still true at exit.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
text_format_parse_digits(const char **ptr, const char *end_ptr, int *value)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
const char *cp = *ptr;
|
||||||
|
int val = 0;
|
||||||
|
|
||||||
|
while (*cp >= '0' && *cp <= '9')
|
||||||
|
{
|
||||||
|
int newval = val * 10 + (*cp - '0');
|
||||||
|
|
||||||
|
if (newval / 10 != val) /* overflow? */
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||||
|
errmsg("number is out of range")));
|
||||||
|
val = newval;
|
||||||
|
ADVANCE_PARSE_POINTER(cp, end_ptr);
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
*ptr = cp;
|
||||||
|
*value = val;
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parse a format specifier (generally following the SUS printf spec).
|
||||||
|
*
|
||||||
|
* We have already advanced over the initial '%', and we are looking for
|
||||||
|
* [argpos][flags][width]type (but the type character is not consumed here).
|
||||||
|
*
|
||||||
|
* Inputs are start_ptr (the position after '%') and end_ptr (string end + 1).
|
||||||
|
* Output parameters:
|
||||||
|
* argpos: argument position for value to be printed. -1 means unspecified.
|
||||||
|
* widthpos: argument position for width. Zero means the argument position
|
||||||
|
* was unspecified (ie, take the next arg) and -1 means no width
|
||||||
|
* argument (width was omitted or specified as a constant).
|
||||||
|
* flags: bitmask of flags.
|
||||||
|
* width: directly-specified width value. Zero means the width was omitted
|
||||||
|
* (note it's not necessary to distinguish this case from an explicit
|
||||||
|
* zero width value).
|
||||||
|
*
|
||||||
|
* The function result is the next character position to be parsed, ie, the
|
||||||
|
* location where the type character is/should be.
|
||||||
|
*
|
||||||
|
* Note parsing invariant: at least one character is known available before
|
||||||
|
* string end (end_ptr) at entry, and this is still true at exit.
|
||||||
|
*/
|
||||||
|
static const char *
|
||||||
|
text_format_parse_format(const char *start_ptr, const char *end_ptr,
|
||||||
|
int *argpos, int *widthpos,
|
||||||
|
int *flags, int *width)
|
||||||
|
{
|
||||||
|
const char *cp = start_ptr;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
/* set defaults for output parameters */
|
||||||
|
*argpos = -1;
|
||||||
|
*widthpos = -1;
|
||||||
|
*flags = 0;
|
||||||
|
*width = 0;
|
||||||
|
|
||||||
|
/* try to identify first number */
|
||||||
|
if (text_format_parse_digits(&cp, end_ptr, &n))
|
||||||
|
{
|
||||||
|
if (*cp != '$')
|
||||||
|
{
|
||||||
|
/* Must be just a width and a type, so we're done */
|
||||||
|
*width = n;
|
||||||
|
return cp;
|
||||||
|
}
|
||||||
|
/* The number was argument position */
|
||||||
|
*argpos = n;
|
||||||
|
/* Explicit 0 for argument index is immediately refused */
|
||||||
|
if (n == 0)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
|
errmsg("format specifies argument 0, but arguments are numbered from 1")));
|
||||||
|
ADVANCE_PARSE_POINTER(cp, end_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle flags (only minus is supported now) */
|
||||||
|
while (*cp == '-')
|
||||||
|
{
|
||||||
|
*flags |= TEXT_FORMAT_FLAG_MINUS;
|
||||||
|
ADVANCE_PARSE_POINTER(cp, end_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*cp == '*')
|
||||||
|
{
|
||||||
|
/* Handle indirect width */
|
||||||
|
ADVANCE_PARSE_POINTER(cp, end_ptr);
|
||||||
|
if (text_format_parse_digits(&cp, end_ptr, &n))
|
||||||
|
{
|
||||||
|
/* number in this position must be closed by $ */
|
||||||
|
if (*cp != '$')
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
|
errmsg("width argument position must be ended by \"$\"")));
|
||||||
|
/* The number was width argument position */
|
||||||
|
*widthpos = n;
|
||||||
|
/* Explicit 0 for argument index is immediately refused */
|
||||||
|
if (n == 0)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
|
errmsg("format specifies argument 0, but arguments are numbered from 1")));
|
||||||
|
ADVANCE_PARSE_POINTER(cp, end_ptr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
*widthpos = 0; /* width's argument position is unspecified */
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Check for direct width specification */
|
||||||
|
if (text_format_parse_digits(&cp, end_ptr, &n))
|
||||||
|
*width = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* cp should now be pointing at type character */
|
||||||
|
return cp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Format a %s, %I, or %L conversion
|
||||||
|
*/
|
||||||
static void
|
static void
|
||||||
text_format_string_conversion(StringInfo buf, char conversion,
|
text_format_string_conversion(StringInfo buf, char conversion,
|
||||||
FmgrInfo *typOutputInfo,
|
FmgrInfo *typOutputInfo,
|
||||||
Datum value, bool isNull)
|
Datum value, bool isNull,
|
||||||
|
int flags, int width)
|
||||||
{
|
{
|
||||||
char *str;
|
char *str;
|
||||||
|
|
||||||
/* Handle NULL arguments before trying to stringify the value. */
|
/* Handle NULL arguments before trying to stringify the value. */
|
||||||
if (isNull)
|
if (isNull)
|
||||||
{
|
{
|
||||||
if (conversion == 'L')
|
if (conversion == 's')
|
||||||
appendStringInfoString(buf, "NULL");
|
text_format_append_string(buf, "", flags, width);
|
||||||
|
else if (conversion == 'L')
|
||||||
|
text_format_append_string(buf, "NULL", flags, width);
|
||||||
else if (conversion == 'I')
|
else if (conversion == 'I')
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
||||||
@ -4271,23 +4458,71 @@ text_format_string_conversion(StringInfo buf, char conversion,
|
|||||||
if (conversion == 'I')
|
if (conversion == 'I')
|
||||||
{
|
{
|
||||||
/* quote_identifier may or may not allocate a new string. */
|
/* quote_identifier may or may not allocate a new string. */
|
||||||
appendStringInfoString(buf, quote_identifier(str));
|
text_format_append_string(buf, quote_identifier(str), flags, width);
|
||||||
}
|
}
|
||||||
else if (conversion == 'L')
|
else if (conversion == 'L')
|
||||||
{
|
{
|
||||||
char *qstr = quote_literal_cstr(str);
|
char *qstr = quote_literal_cstr(str);
|
||||||
|
|
||||||
appendStringInfoString(buf, qstr);
|
text_format_append_string(buf, qstr, flags, width);
|
||||||
/* quote_literal_cstr() always allocates a new string */
|
/* quote_literal_cstr() always allocates a new string */
|
||||||
pfree(qstr);
|
pfree(qstr);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
appendStringInfoString(buf, str);
|
text_format_append_string(buf, str, flags, width);
|
||||||
|
|
||||||
/* Cleanup. */
|
/* Cleanup. */
|
||||||
pfree(str);
|
pfree(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Append str to buf, padding as directed by flags/width
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
text_format_append_string(StringInfo buf, const char *str,
|
||||||
|
int flags, int width)
|
||||||
|
{
|
||||||
|
bool align_to_left = false;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
/* fast path for typical easy case */
|
||||||
|
if (width == 0)
|
||||||
|
{
|
||||||
|
appendStringInfoString(buf, str);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width < 0)
|
||||||
|
{
|
||||||
|
/* Negative width: implicit '-' flag, then take absolute value */
|
||||||
|
align_to_left = true;
|
||||||
|
/* -INT_MIN is undefined */
|
||||||
|
if (width <= INT_MIN)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||||
|
errmsg("number is out of range")));
|
||||||
|
width = -width;
|
||||||
|
}
|
||||||
|
else if (flags & TEXT_FORMAT_FLAG_MINUS)
|
||||||
|
align_to_left = true;
|
||||||
|
|
||||||
|
len = pg_mbstrlen(str);
|
||||||
|
if (align_to_left)
|
||||||
|
{
|
||||||
|
/* left justify */
|
||||||
|
appendStringInfoString(buf, str);
|
||||||
|
if (len < width)
|
||||||
|
appendStringInfoSpaces(buf, width - len);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* right justify */
|
||||||
|
if (len < width)
|
||||||
|
appendStringInfoSpaces(buf, width - len);
|
||||||
|
appendStringInfoString(buf, str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* text_format_nv - nonvariadic wrapper for text_format function.
|
* text_format_nv - nonvariadic wrapper for text_format function.
|
||||||
*
|
*
|
||||||
|
@ -209,7 +209,7 @@ ERROR: too few arguments for format
|
|||||||
select format('Hello %s');
|
select format('Hello %s');
|
||||||
ERROR: too few arguments for format
|
ERROR: too few arguments for format
|
||||||
select format('Hello %x', 20);
|
select format('Hello %x', 20);
|
||||||
ERROR: unrecognized conversion specifier "x"
|
ERROR: unrecognized conversion type specifier "x"
|
||||||
-- check literal and sql identifiers
|
-- check literal and sql identifiers
|
||||||
select format('INSERT INTO %I VALUES(%L,%L)', 'mytab', 10, 'Hello');
|
select format('INSERT INTO %I VALUES(%L,%L)', 'mytab', 10, 'Hello');
|
||||||
format
|
format
|
||||||
@ -256,12 +256,14 @@ select format('%1$s %4$s', 1, 2, 3);
|
|||||||
ERROR: too few arguments for format
|
ERROR: too few arguments for format
|
||||||
select format('%1$s %13$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
|
select format('%1$s %13$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
|
||||||
ERROR: too few arguments for format
|
ERROR: too few arguments for format
|
||||||
select format('%1s', 1);
|
select format('%0$s', 'Hello');
|
||||||
ERROR: unterminated conversion specifier
|
ERROR: format specifies argument 0, but arguments are numbered from 1
|
||||||
|
select format('%*0$s', 'Hello');
|
||||||
|
ERROR: format specifies argument 0, but arguments are numbered from 1
|
||||||
select format('%1$', 1);
|
select format('%1$', 1);
|
||||||
ERROR: unterminated conversion specifier
|
ERROR: unterminated format specifier
|
||||||
select format('%1$1', 1);
|
select format('%1$1', 1);
|
||||||
ERROR: unrecognized conversion specifier "1"
|
ERROR: unterminated format specifier
|
||||||
-- check mix of positional and ordered placeholders
|
-- check mix of positional and ordered placeholders
|
||||||
select format('Hello %s %1$s %s', 'World', 'Hello again');
|
select format('Hello %s %1$s %s', 'World', 'Hello again');
|
||||||
format
|
format
|
||||||
@ -328,3 +330,106 @@ from generate_series(1,200) g(i);
|
|||||||
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,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)
|
(1 row)
|
||||||
|
|
||||||
|
-- check field widths and left, right alignment
|
||||||
|
select format('>>%10s<<', 'Hello');
|
||||||
|
format
|
||||||
|
----------------
|
||||||
|
>> Hello<<
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select format('>>%10s<<', NULL);
|
||||||
|
format
|
||||||
|
----------------
|
||||||
|
>> <<
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select format('>>%10s<<', '');
|
||||||
|
format
|
||||||
|
----------------
|
||||||
|
>> <<
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select format('>>%-10s<<', '');
|
||||||
|
format
|
||||||
|
----------------
|
||||||
|
>> <<
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select format('>>%-10s<<', 'Hello');
|
||||||
|
format
|
||||||
|
----------------
|
||||||
|
>>Hello <<
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select format('>>%-10s<<', NULL);
|
||||||
|
format
|
||||||
|
----------------
|
||||||
|
>> <<
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select format('>>%1$10s<<', 'Hello');
|
||||||
|
format
|
||||||
|
----------------
|
||||||
|
>> Hello<<
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select format('>>%1$-10I<<', 'Hello');
|
||||||
|
format
|
||||||
|
----------------
|
||||||
|
>>"Hello" <<
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select format('>>%2$*1$L<<', 10, 'Hello');
|
||||||
|
format
|
||||||
|
----------------
|
||||||
|
>> 'Hello'<<
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select format('>>%2$*1$L<<', 10, NULL);
|
||||||
|
format
|
||||||
|
----------------
|
||||||
|
>> NULL<<
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select format('>>%2$*1$L<<', -10, NULL);
|
||||||
|
format
|
||||||
|
----------------
|
||||||
|
>>NULL <<
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select format('>>%*s<<', 10, 'Hello');
|
||||||
|
format
|
||||||
|
----------------
|
||||||
|
>> Hello<<
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select format('>>%*1$s<<', 10, 'Hello');
|
||||||
|
format
|
||||||
|
----------------
|
||||||
|
>> Hello<<
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select format('>>%-s<<', 'Hello');
|
||||||
|
format
|
||||||
|
-----------
|
||||||
|
>>Hello<<
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select format('>>%10L<<', NULL);
|
||||||
|
format
|
||||||
|
----------------
|
||||||
|
>> NULL<<
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select format('>>%2$*1$L<<', NULL, 'Hello');
|
||||||
|
format
|
||||||
|
-------------
|
||||||
|
>>'Hello'<<
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select format('>>%2$*1$L<<', 0, 'Hello');
|
||||||
|
format
|
||||||
|
-------------
|
||||||
|
>>'Hello'<<
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
@ -78,7 +78,8 @@ select format('%1$s %12$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
|
|||||||
-- should fail
|
-- should fail
|
||||||
select format('%1$s %4$s', 1, 2, 3);
|
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('%1$s %13$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
|
||||||
select format('%1s', 1);
|
select format('%0$s', 'Hello');
|
||||||
|
select format('%*0$s', 'Hello');
|
||||||
select format('%1$', 1);
|
select format('%1$', 1);
|
||||||
select format('%1$1', 1);
|
select format('%1$1', 1);
|
||||||
-- check mix of positional and ordered placeholders
|
-- check mix of positional and ordered placeholders
|
||||||
@ -97,3 +98,21 @@ select format('Hello', variadic NULL);
|
|||||||
-- variadic argument allows simulating more than FUNC_MAX_ARGS parameters
|
-- variadic argument allows simulating more than FUNC_MAX_ARGS parameters
|
||||||
select format(string_agg('%s',','), variadic array_agg(i))
|
select format(string_agg('%s',','), variadic array_agg(i))
|
||||||
from generate_series(1,200) g(i);
|
from generate_series(1,200) g(i);
|
||||||
|
-- check field widths and left, right alignment
|
||||||
|
select format('>>%10s<<', 'Hello');
|
||||||
|
select format('>>%10s<<', NULL);
|
||||||
|
select format('>>%10s<<', '');
|
||||||
|
select format('>>%-10s<<', '');
|
||||||
|
select format('>>%-10s<<', 'Hello');
|
||||||
|
select format('>>%-10s<<', NULL);
|
||||||
|
select format('>>%1$10s<<', 'Hello');
|
||||||
|
select format('>>%1$-10I<<', 'Hello');
|
||||||
|
select format('>>%2$*1$L<<', 10, 'Hello');
|
||||||
|
select format('>>%2$*1$L<<', 10, NULL);
|
||||||
|
select format('>>%2$*1$L<<', -10, NULL);
|
||||||
|
select format('>>%*s<<', 10, 'Hello');
|
||||||
|
select format('>>%*1$s<<', 10, 'Hello');
|
||||||
|
select format('>>%-s<<', 'Hello');
|
||||||
|
select format('>>%10L<<', NULL);
|
||||||
|
select format('>>%2$*1$L<<', NULL, 'Hello');
|
||||||
|
select format('>>%2$*1$L<<', 0, 'Hello');
|
||||||
|
Loading…
Reference in New Issue
Block a user