]> granicus.if.org Git - postgresql/commitdiff
Fix concat() and format() to handle VARIADIC-labeled arguments correctly.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 25 Jan 2013 05:19:18 +0000 (00:19 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 25 Jan 2013 05:19:56 +0000 (00:19 -0500)
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

doc/src/sgml/func.sgml
doc/src/sgml/xfunc.sgml
src/backend/utils/adt/varlena.c
src/test/regress/expected/text.out
src/test/regress/sql/text.sql

index 35c7f75eab2626c3d7a644c39e42101d557b995c..e9dbcb9f8a005731f863b575d44cd2c8b4baa3ad 100644 (file)
        </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>
        </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>
        </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
     </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">.
index 85539feb0d255daf486246dee911802b1a274e17..4fb42842c6f88d1e77f8fd4b3cea475194c67e45 100644 (file)
@@ -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>
 
index 95e41bf30af1d5958d628b72065abe38b4a98430..e69b7dd3e6b6d5f966fda7a9ccffbd898af519e5 100644 (file)
@@ -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')
index e45714f77ecd1dcef0160036ce5f6b30d30c5d59..b7565830d6f165035625b9c856c6a15118d2f731 100644 (file)
@@ -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)
+
index 96e425d3cf70509e3188b8f94ecf313da46d55cd..a96e9f7d1e7e4e3817e67f1223f74ed6a3d7142c 100644 (file)
@@ -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);