提交 760f3c04 编写于 作者: T Tom Lane

Fix concat() and format() to handle VARIADIC-labeled arguments correctly.

Previously, the VARIADIC labeling was effectively ignored, but now these
functions act as though the array elements had all been given as separate
arguments.

Pavel Stehule
上级 56a6317b
......@@ -1394,7 +1394,8 @@
</entry>
<entry><type>text</type></entry>
<entry>
Concatenate all arguments. NULL arguments are ignored.
Concatenate the text representations of all the arguments.
NULL arguments are ignored.
</entry>
<entry><literal>concat('abcde', 2, NULL, 22)</literal></entry>
<entry><literal>abcde222</literal></entry>
......@@ -1411,8 +1412,8 @@
</entry>
<entry><type>text</type></entry>
<entry>
Concatenate all but first arguments with separators. The first
parameter is used as a separator. NULL arguments are ignored.
Concatenate all but the first argument with separators. The first
argument is used as the separator string. NULL arguments are ignored.
</entry>
<entry><literal>concat_ws(',', 'abcde', 2, NULL, 22)</literal></entry>
<entry><literal>abcde,2,22</literal></entry>
......@@ -1522,8 +1523,9 @@
</entry>
<entry><type>text</type></entry>
<entry>
Format a string. This function is similar to the C function
<function>sprintf</>; but only the following conversion specifications
Format arguments according to a format string.
This function is similar to the C function
<function>sprintf</>, but only the following conversion specifications
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
......@@ -2033,6 +2035,18 @@
</tgroup>
</table>
<para>
The <function>concat</function>, <function>concat_ws</function> and
<function>format</function> functions are variadic, so it is possible to
pass the values to be concatenated or formatted as an array marked with
the <literal>VARIADIC</literal> keyword (see <xref
linkend="xfunc-sql-variadic-functions">). The array's elements are
treated as if they were separate ordinary arguments to the function.
If the variadic array argument is NULL, <function>concat</function>
and <function>concat_ws</function> return NULL, but
<function>format</function> treats a NULL as a zero-element array.
</para>
<para>
See also the aggregate function <function>string_agg</function> in
<xref linkend="functions-aggregate">.
......
......@@ -3153,6 +3153,10 @@ CREATE OR REPLACE FUNCTION retcomposite(IN integer, IN integer,
<literal>fcinfo-&gt;flinfo</>. The parameter <literal>argnum</>
is zero based. <function>get_call_result_type</> can also be used
as an alternative to <function>get_fn_expr_rettype</>.
There is also <function>get_fn_expr_variadic</>, which can be used to
find out whether the call contained an explicit <literal>VARIADIC</>
keyword. This is primarily useful for <literal>VARIADIC "any"</>
functions, as described below.
</para>
<para>
......@@ -3229,7 +3233,12 @@ CREATE FUNCTION make_array(anyelement) RETURNS anyarray
as happens with normal variadic functions; they will just be passed to
the function separately. The <function>PG_NARGS()</> macro and the
methods described above must be used to determine the number of actual
arguments and their types when using this feature.
arguments and their types when using this feature. Also, users of such
a function might wish to use the <literal>VARIADIC</> keyword in their
function call, with the expectation that the function would treat the
array elements as separate arguments. The function itself must implement
that behavior if wanted, after using <function>get_fn_expr_variadic</> to
detect that the actual argument was marked with <literal>VARIADIC</>.
</para>
</sect2>
......
......@@ -76,12 +76,12 @@ static bytea *bytea_substring(Datum str,
bool length_not_specified);
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 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,
char *fldsep, char *null_string);
const char *fldsep, const char *null_string);
/*****************************************************************************
......@@ -3451,7 +3451,7 @@ array_to_text_null(PG_FUNCTION_ARGS)
*/
static text *
array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
char *fldsep, char *null_string)
const char *fldsep, const char *null_string)
{
text *result;
int nitems,
......@@ -3791,11 +3791,12 @@ string_agg_finalfn(PG_FUNCTION_ARGS)
/*
* Implementation of both concat() and concat_ws().
*
* sepstr/seplen describe the separator. argidx is the first argument
* to concatenate (counting from zero).
* sepstr is the separator string to place between values.
* argidx identifies the first argument to concatenate (counting from zero).
* Returns NULL if result should be NULL, else text value.
*/
static text *
concat_internal(const char *sepstr, int seplen, int argidx,
concat_internal(const char *sepstr, int argidx,
FunctionCallInfo fcinfo)
{
text *result;
......@@ -3803,6 +3804,48 @@ concat_internal(const char *sepstr, int seplen, int argidx,
bool first_arg = true;
int i;
/*
* concat(VARIADIC some-array) is essentially equivalent to
* array_to_text(), ie concat the array elements with the given separator.
* So we just pass the case off to that code.
*/
if (get_fn_expr_variadic(fcinfo->flinfo))
{
Oid arr_typid;
ArrayType *arr;
/* Should have just the one argument */
Assert(argidx == PG_NARGS() - 1);
/* concat(VARIADIC NULL) is defined as NULL */
if (PG_ARGISNULL(argidx))
return NULL;
/*
* Non-null argument had better be an array. The parser doesn't
* enforce this for VARIADIC ANY functions (maybe it should?), so that
* check uses ereport not just elog.
*/
arr_typid = get_fn_expr_argtype(fcinfo->flinfo, argidx);
if (!OidIsValid(arr_typid))
elog(ERROR, "could not determine data type of concat() input");
if (!OidIsValid(get_element_type(arr_typid)))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("VARIADIC argument must be an array")));
/* OK, safe to fetch the array value */
arr = PG_GETARG_ARRAYTYPE_P(argidx);
/*
* And serialize the array. We tell array_to_text to ignore null
* elements, which matches the behavior of the loop below.
*/
return array_to_text_internal(fcinfo, arr, sepstr, NULL);
}
/* Normal case without explicit VARIADIC marker */
initStringInfo(&str);
for (i = argidx; i < PG_NARGS(); i++)
......@@ -3818,7 +3861,7 @@ concat_internal(const char *sepstr, int seplen, int argidx,
if (first_arg)
first_arg = false;
else
appendBinaryStringInfo(&str, sepstr, seplen);
appendStringInfoString(&str, sepstr);
/* call the appropriate type output function, append the result */
valtype = get_fn_expr_argtype(fcinfo->flinfo, i);
......@@ -3842,7 +3885,12 @@ concat_internal(const char *sepstr, int seplen, int argidx,
Datum
text_concat(PG_FUNCTION_ARGS)
{
PG_RETURN_TEXT_P(concat_internal("", 0, 0, fcinfo));
text *result;
result = concat_internal("", 0, fcinfo);
if (result == NULL)
PG_RETURN_NULL();
PG_RETURN_TEXT_P(result);
}
/*
......@@ -3852,16 +3900,18 @@ text_concat(PG_FUNCTION_ARGS)
Datum
text_concat_ws(PG_FUNCTION_ARGS)
{
text *sep;
char *sep;
text *result;
/* return NULL when separator is NULL */
if (PG_ARGISNULL(0))
PG_RETURN_NULL();
sep = text_to_cstring(PG_GETARG_TEXT_PP(0));
sep = PG_GETARG_TEXT_PP(0);
PG_RETURN_TEXT_P(concat_internal(VARDATA_ANY(sep), VARSIZE_ANY_EXHDR(sep),
1, fcinfo));
result = concat_internal(sep, 1, fcinfo);
if (result == NULL)
PG_RETURN_NULL();
PG_RETURN_TEXT_P(result);
}
/*
......@@ -3959,11 +4009,73 @@ text_format(PG_FUNCTION_ARGS)
const char *end_ptr;
text *result;
int arg = 0;
bool funcvariadic;
int nargs;
Datum *elements = NULL;
bool *nulls = NULL;
Oid element_type = InvalidOid;
Oid prev_type = InvalidOid;
FmgrInfo typoutputfinfo;
/* When format string is null, returns null */
if (PG_ARGISNULL(0))
PG_RETURN_NULL();
/* If argument is marked VARIADIC, expand array into elements */
if (get_fn_expr_variadic(fcinfo->flinfo))
{
Oid arr_typid;
ArrayType *arr;
int16 elmlen;
bool elmbyval;
char elmalign;
int nitems;
/* Should have just the one argument */
Assert(PG_NARGS() == 2);
/* If argument is NULL, we treat it as zero-length array */
if (PG_ARGISNULL(1))
nitems = 0;
else
{
/*
* Non-null argument had better be an array. The parser doesn't
* enforce this for VARIADIC ANY functions (maybe it should?), so
* that check uses ereport not just elog.
*/
arr_typid = get_fn_expr_argtype(fcinfo->flinfo, 1);
if (!OidIsValid(arr_typid))
elog(ERROR, "could not determine data type of format() input");
if (!OidIsValid(get_element_type(arr_typid)))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("VARIADIC argument must be an array")));
/* OK, safe to fetch the array value */
arr = PG_GETARG_ARRAYTYPE_P(1);
/* Get info about array element type */
element_type = ARR_ELEMTYPE(arr);
get_typlenbyvalalign(element_type,
&elmlen, &elmbyval, &elmalign);
/* Extract all array elements */
deconstruct_array(arr, element_type, elmlen, elmbyval, elmalign,
&elements, &nulls, &nitems);
}
nargs = nitems + 1;
funcvariadic = true;
}
else
{
/* Non-variadic case, we'll process the arguments individually */
nargs = PG_NARGS();
funcvariadic = false;
}
/* Setup for main loop. */
fmt = PG_GETARG_TEXT_PP(0);
start_ptr = VARDATA_ANY(fmt);
......@@ -4062,26 +4174,54 @@ text_format(PG_FUNCTION_ARGS)
}
/* Not enough arguments? Deduct 1 to avoid counting format string. */
if (arg > PG_NARGS() - 1)
if (arg > nargs - 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("too few arguments for format")));
/* Get the value and type of the selected argument */
if (!funcvariadic)
{
value = PG_GETARG_DATUM(arg);
isNull = PG_ARGISNULL(arg);
typid = get_fn_expr_argtype(fcinfo->flinfo, arg);
}
else
{
value = elements[arg - 1];
isNull = nulls[arg - 1];
typid = element_type;
}
if (!OidIsValid(typid))
elog(ERROR, "could not determine data type of format() input");
/*
* Get the appropriate typOutput function, reusing previous one if
* same type as previous argument. That's particularly useful in the
* variadic-array case, but often saves work even for ordinary calls.
*/
if (typid != prev_type)
{
Oid typoutputfunc;
bool typIsVarlena;
getTypeOutputInfo(typid, &typoutputfunc, &typIsVarlena);
fmgr_info(typoutputfunc, &typoutputfinfo);
prev_type = typid;
}
/*
* At this point, we should see the main conversion specifier. Whether
* or not an argument position was present, it's known that at least
* one character remains in the string at this point.
*/
value = PG_GETARG_DATUM(arg);
isNull = PG_ARGISNULL(arg);
typid = get_fn_expr_argtype(fcinfo->flinfo, arg);
switch (*cp)
{
case 's':
case 'I':
case 'L':
text_format_string_conversion(&str, *cp, typid, value, isNull);
text_format_string_conversion(&str, *cp, &typoutputfinfo,
value, isNull);
break;
default:
ereport(ERROR,
......@@ -4091,6 +4231,12 @@ text_format(PG_FUNCTION_ARGS)
}
}
/* Don't need deconstruct_array results anymore. */
if (elements != NULL)
pfree(elements);
if (nulls != NULL)
pfree(nulls);
/* Generate results. */
result = cstring_to_text_with_len(str.data, str.len);
pfree(str.data);
......@@ -4099,12 +4245,11 @@ text_format(PG_FUNCTION_ARGS)
}
/* Format a %s, %I, or %L conversion. */
void
static void
text_format_string_conversion(StringInfo buf, char conversion,
Oid typid, Datum value, bool isNull)
FmgrInfo *typOutputInfo,
Datum value, bool isNull)
{
Oid typOutput;
bool typIsVarlena;
char *str;
/* Handle NULL arguments before trying to stringify the value. */
......@@ -4120,8 +4265,7 @@ text_format_string_conversion(StringInfo buf, char conversion,
}
/* Stringify. */
getTypeOutputInfo(typid, &typOutput, &typIsVarlena);
str = OidOutputFunctionCall(typOutput, value);
str = OutputFunctionCall(typOutputInfo, value);
/* Escape. */
if (conversion == 'I')
......
......@@ -136,6 +136,40 @@ select quote_literal(e'\\');
E'\\'
(1 row)
-- check variadic labeled argument
select concat(variadic array[1,2,3]);
concat
--------
123
(1 row)
select concat_ws(',', variadic array[1,2,3]);
concat_ws
-----------
1,2,3
(1 row)
select concat_ws(',', variadic NULL);
concat_ws
-----------
(1 row)
select concat(variadic NULL) is NULL;
?column?
----------
t
(1 row)
select concat(variadic '{}'::int[]) = '';
?column?
----------
t
(1 row)
--should fail
select concat_ws(',', variadic 10);
ERROR: VARIADIC argument must be an array
/*
* format
*/
......@@ -228,7 +262,7 @@ 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
-- check mix of positional and ordered placeholders
select format('Hello %s %1$s %s', 'World', 'Hello again');
format
-------------------------------
......@@ -241,3 +275,56 @@ select format('Hello %s %s, %2$s %2$s', 'World', 'Hello again');
Hello World Hello again, Hello again Hello again
(1 row)
-- check variadic labeled arguments
select format('%s, %s', variadic array['Hello','World']);
format
--------------
Hello, World
(1 row)
select format('%s, %s', variadic array[1, 2]);
format
--------
1, 2
(1 row)
select format('%s, %s', variadic array[true, false]);
format
--------
t, f
(1 row)
select format('%s, %s', variadic array[true, false]::text[]);
format
-------------
true, false
(1 row)
-- check variadic with positional placeholders
select format('%2$s, %1$s', variadic array['first', 'second']);
format
---------------
second, first
(1 row)
select format('%2$s, %1$s', variadic array[1, 2]);
format
--------
2, 1
(1 row)
-- variadic argument can be NULL, but should not be referenced
select format('Hello', variadic NULL);
format
--------
Hello
(1 row)
-- 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
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
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)
......@@ -44,6 +44,14 @@ select i, left('ahoj', i), right('ahoj', i) from generate_series(-5, 5) t(i) ord
select quote_literal('');
select quote_literal('abc''');
select quote_literal(e'\\');
-- check variadic labeled argument
select concat(variadic array[1,2,3]);
select concat_ws(',', variadic array[1,2,3]);
select concat_ws(',', variadic NULL);
select concat(variadic NULL) is NULL;
select concat(variadic '{}'::int[]) = '';
--should fail
select concat_ws(',', variadic 10);
/*
* format
......@@ -73,6 +81,19 @@ 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
-- check 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');
-- check variadic labeled arguments
select format('%s, %s', variadic array['Hello','World']);
select format('%s, %s', variadic array[1, 2]);
select format('%s, %s', variadic array[true, false]);
select format('%s, %s', variadic array[true, false]::text[]);
-- check variadic with positional placeholders
select format('%2$s, %1$s', variadic array['first', 'second']);
select format('%2$s, %1$s', variadic array[1, 2]);
-- variadic argument can be NULL, but should not be referenced
select format('Hello', variadic NULL);
-- 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);
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册