-<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.525 2010/08/08 19:15:27 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.526 2010/08/10 21:51:00 tgl Exp $ -->
 
  <chapter id="functions">
   <title>Functions and Operators</title>
        <row>
         <entry>
          <literal>
-          <function>array_to_string</function>(<type>anyarray</type>, <type>text</type>)
+          <function>array_to_string</function>(<type>anyarray</type>, <type>text</type> <optional>, <type>text</type></optional>)
          </literal>
         </entry>
         <entry><type>text</type></entry>
-        <entry>concatenates array elements using supplied delimiter</entry>
-        <entry><literal>array_to_string(ARRAY[1, 2, 3], '~^~')</literal></entry>
-        <entry><literal>1~^~2~^~3</literal></entry>
+        <entry>concatenates array elements using supplied delimiter and
+         optional null string</entry>
+        <entry><literal>array_to_string(ARRAY[1, 2, 3, NULL, 5], ',', '*')</literal></entry>
+        <entry><literal>1,2,3,*,5</literal></entry>
        </row>
        <row>
         <entry>
        <row>
         <entry>
          <literal>
-          <function>string_to_array</function>(<type>text</type>, <type>text</type>)
+          <function>string_to_array</function>(<type>text</type>, <type>text</type> <optional>, <type>text</type></optional>)
          </literal>
         </entry>
         <entry><type>text[]</type></entry>
-        <entry>splits string into array elements using supplied delimiter</entry>
-        <entry><literal>string_to_array('xx~^~yy~^~zz', '~^~')</literal></entry>
-        <entry><literal>{xx,yy,zz}</literal></entry>
+        <entry>splits string into array elements using supplied delimiter and
+         optional null string</entry>
+        <entry><literal>string_to_array('xx~^~yy~^~zz', '~^~', 'yy')</literal></entry>
+        <entry><literal>{xx,NULL,zz}</literal></entry>
        </row>
        <row>
         <entry>
      </tgroup>
     </table>
 
+   <para>
+    In <function>string_to_array</function>, if the delimiter parameter is
+    NULL, each character in the input string will become a separate element in
+    the resulting array.  If the delimiter is an empty string, then the entire
+    input string is returned as a one-element array.  Otherwise the input
+    string is split at each occurrence of the delimiter string.
+   </para>
+
+   <para>
+    In <function>string_to_array</function>, if the null-string parameter
+    is omitted or NULL, none of the substrings of the input will be replaced
+    by NULL.
+    In <function>array_to_string</function>, if the null-string parameter
+    is omitted or NULL, any null elements in the array are simply skipped
+    and not represented in the output string.
+   </para>
+
+   <note>
+    <para>
+     There are two differences in the behavior of <function>string_to_array</>
+     from pre-9.1 versions of <productname>PostgreSQL</>.
+     First, it will return an empty (zero-element) array rather than NULL when
+     the input string is of zero length.  Second, if the delimiter string is
+     NULL, the function splits the input into individual characters, rather
+     than returning NULL as before.
+    </para>
+   </note>
+
    <para>
     See also <xref linkend="functions-aggregate"> about the aggregate
     function <function>array_agg</function> for use with arrays.
 
  * Copyright (c) 2003-2010, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/adt/array_userfuncs.c,v 1.35 2010/02/26 02:01:06 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/array_userfuncs.c,v 1.36 2010/08/10 21:51:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 create_singleton_array(FunctionCallInfo fcinfo,
                                           Oid element_type,
                                           Datum element,
+                                          bool isNull,
                                           int ndims)
 {
        Datum           dvalues[1];
+       bool            nulls[1];
        int16           typlen;
        bool            typbyval;
        char            typalign;
                                                ndims, MAXDIM)));
 
        dvalues[0] = element;
+       nulls[0] = isNull;
 
        for (i = 0; i < ndims; i++)
        {
        typbyval = my_extra->typbyval;
        typalign = my_extra->typalign;
 
-       return construct_md_array(dvalues, NULL, ndims, dims, lbs, element_type,
+       return construct_md_array(dvalues, nulls, ndims, dims, lbs, element_type,
                                                          typlen, typbyval, typalign);
 }
 
 
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/adt/varlena.c,v 1.178 2010/08/05 18:21:17 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/varlena.c,v 1.179 2010/08/10 21:51:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 static bytea *bytea_overlay(bytea *t1, bytea *t2, int sp, int sl);
 static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
 
+static Datum text_to_array_internal(PG_FUNCTION_ARGS);
+static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
+                                                                       char *fldsep, char *null_string);
+
 
 /*****************************************************************************
  *      CONVERSION ROUTINES EXPORTED FOR USE BY C CODE                                                  *
        PG_RETURN_TEXT_P(result_text);
 }
 
+/*
+ * Convenience function to return true when two text params are equal.
+ */
+static bool
+text_isequal(text *txt1, text *txt2)
+{
+       return DatumGetBool(DirectFunctionCall2(texteq,
+                                                                                       PointerGetDatum(txt1),
+                                                                                       PointerGetDatum(txt2)));
+}
+
 /*
  * text_to_array
- * parse input string
- * return text array of elements
+ * parse input string and return text array of elements,
  * based on provided field separator
  */
 Datum
 text_to_array(PG_FUNCTION_ARGS)
 {
-       text       *inputstring = PG_GETARG_TEXT_PP(0);
-       text       *fldsep = PG_GETARG_TEXT_PP(1);
+       return text_to_array_internal(fcinfo);
+}
+
+/*
+ * text_to_array_null
+ * parse input string and return text array of elements,
+ * based on provided field separator and null string
+ *
+ * This is a separate entry point only to prevent the regression tests from
+ * complaining about different argument sets for the same internal function.
+ */
+Datum
+text_to_array_null(PG_FUNCTION_ARGS)
+{
+       return text_to_array_internal(fcinfo);
+}
+
+/*
+ * common code for text_to_array and text_to_array_null functions
+ *
+ * These are not strict so we have to test for null inputs explicitly.
+ */
+static Datum
+text_to_array_internal(PG_FUNCTION_ARGS)
+{
+       text       *inputstring;
+       text       *fldsep;
+       text       *null_string;
        int                     inputstring_len;
        int                     fldsep_len;
-       TextPositionState state;
-       int                     fldnum;
-       int                     start_posn;
-       int                     end_posn;
-       int                     chunk_len;
        char       *start_ptr;
        text       *result_text;
+       bool            is_null;
        ArrayBuildState *astate = NULL;
 
-       text_position_setup(inputstring, fldsep, &state);
-
-       /*
-        * Note: we check the converted string length, not the original, because
-        * they could be different if the input contained invalid encoding.
-        */
-       inputstring_len = state.len1;
-       fldsep_len = state.len2;
-
-       /* return NULL for empty input string */
-       if (inputstring_len < 1)
-       {
-               text_position_cleanup(&state);
+       /* when input string is NULL, then result is NULL too */
+       if (PG_ARGISNULL(0))
                PG_RETURN_NULL();
-       }
 
-       /*
-        * empty field separator return one element, 1D, array using the input
-        * string
-        */
-       if (fldsep_len < 1)
-       {
-               text_position_cleanup(&state);
-               PG_RETURN_ARRAYTYPE_P(create_singleton_array(fcinfo, TEXTOID,
-                                                                                  PointerGetDatum(inputstring), 1));
-       }
+       inputstring = PG_GETARG_TEXT_PP(0);
 
-       start_posn = 1;
-       /* start_ptr points to the start_posn'th character of inputstring */
-       start_ptr = VARDATA_ANY(inputstring);
+       /* fldsep can be NULL */
+       if (!PG_ARGISNULL(1))
+               fldsep = PG_GETARG_TEXT_PP(1);
+       else
+               fldsep = NULL;
+
+       /* null_string can be NULL or omitted */
+       if (PG_NARGS() > 2 && !PG_ARGISNULL(2))
+               null_string = PG_GETARG_TEXT_PP(2);
+       else
+               null_string = NULL;
 
-       for (fldnum = 1;; fldnum++) /* field number is 1 based */
+       if (fldsep != NULL)
        {
-               CHECK_FOR_INTERRUPTS();
+               /*
+                * Normal case with non-null fldsep.  Use the text_position machinery
+                * to search for occurrences of fldsep.
+                */
+               TextPositionState state;
+               int                     fldnum;
+               int                     start_posn;
+               int                     end_posn;
+               int                     chunk_len;
+               
+               text_position_setup(inputstring, fldsep, &state);
 
-               end_posn = text_position_next(start_posn, &state);
+               /*
+                * Note: we check the converted string length, not the original,
+                * because they could be different if the input contained invalid
+                * encoding.
+                */
+               inputstring_len = state.len1;
+               fldsep_len = state.len2;
 
-               if (end_posn == 0)
+               /* return empty array for empty input string */
+               if (inputstring_len < 1)
                {
-                       /* fetch last field */
-                       chunk_len = ((char *) inputstring + VARSIZE_ANY(inputstring)) - start_ptr;
+                       text_position_cleanup(&state);
+                       PG_RETURN_ARRAYTYPE_P(construct_empty_array(TEXTOID));
                }
-               else
+
+               /*
+                * empty field separator: return the input string as a one-element
+                * array
+                */
+               if (fldsep_len < 1)
                {
-                       /* fetch non-last field */
-                       chunk_len = charlen_to_bytelen(start_ptr, end_posn - start_posn);
+                       text_position_cleanup(&state);
+                       /* single element can be a NULL too */
+                       is_null = null_string ? text_isequal(inputstring, null_string) : false;
+                       PG_RETURN_ARRAYTYPE_P(create_singleton_array(fcinfo, TEXTOID,
+                                                                                                                PointerGetDatum(inputstring),
+                                                                                                                is_null, 1));
                }
+               
+               start_posn = 1;
+               /* start_ptr points to the start_posn'th character of inputstring */
+               start_ptr = VARDATA_ANY(inputstring);
+
+               for (fldnum = 1;; fldnum++) /* field number is 1 based */
+               {
+                       CHECK_FOR_INTERRUPTS();
 
-               /* must build a temp text datum to pass to accumArrayResult */
-               result_text = cstring_to_text_with_len(start_ptr, chunk_len);
+                       end_posn = text_position_next(start_posn, &state);
 
-               /* stash away this field */
-               astate = accumArrayResult(astate,
-                                                                 PointerGetDatum(result_text),
-                                                                 false,
-                                                                 TEXTOID,
-                                                                 CurrentMemoryContext);
+                       if (end_posn == 0)
+                       {
+                               /* fetch last field */
+                               chunk_len = ((char *) inputstring + VARSIZE_ANY(inputstring)) - start_ptr;
+                       }
+                       else
+                       {
+                               /* fetch non-last field */
+                               chunk_len = charlen_to_bytelen(start_ptr, end_posn - start_posn);
+                       }
 
-               pfree(result_text);
+                       /* must build a temp text datum to pass to accumArrayResult */
+                       result_text = cstring_to_text_with_len(start_ptr, chunk_len);
+                       is_null = null_string ? text_isequal(result_text, null_string) : false;
+               
+                       /* stash away this field */
+                       astate = accumArrayResult(astate,
+                                                                         PointerGetDatum(result_text),
+                                                                         is_null,
+                                                                         TEXTOID,
+                                                                         CurrentMemoryContext);
 
-               if (end_posn == 0)
-                       break;
+                       pfree(result_text);
 
-               start_posn = end_posn;
-               start_ptr += chunk_len;
-               start_posn += fldsep_len;
-               start_ptr += charlen_to_bytelen(start_ptr, fldsep_len);
+                       if (end_posn == 0)
+                               break;
+
+                       start_posn = end_posn;
+                       start_ptr += chunk_len;
+                       start_posn += fldsep_len;
+                       start_ptr += charlen_to_bytelen(start_ptr, fldsep_len);
+               }
+
+               text_position_cleanup(&state);
        }
+       else
+       {
+               /* 
+                * When fldsep is NULL, each character in the inputstring becomes an
+                * element in the result array.  The separator is effectively the space
+                * between characters.
+                */
+               inputstring_len = VARSIZE_ANY_EXHDR(inputstring);
+               
+               /* return empty array for empty input string */
+               if (inputstring_len < 1)
+                       PG_RETURN_ARRAYTYPE_P(construct_empty_array(TEXTOID));
+               
+               start_ptr = VARDATA_ANY(inputstring);
+               
+               while (inputstring_len > 0)
+               {
+                       int             chunk_len = pg_mblen(start_ptr);
 
-       text_position_cleanup(&state);
+                       CHECK_FOR_INTERRUPTS();
+
+                       /* must build a temp text datum to pass to accumArrayResult */
+                       result_text = cstring_to_text_with_len(start_ptr, chunk_len);
+                       is_null = null_string ? text_isequal(result_text, null_string) : false;
+               
+                       /* stash away this field */
+                       astate = accumArrayResult(astate,
+                                                                         PointerGetDatum(result_text),
+                                                                         is_null,
+                                                                         TEXTOID,
+                                                                         CurrentMemoryContext);
+
+                       pfree(result_text);
+
+                       start_ptr += chunk_len;
+                       inputstring_len -= chunk_len;
+               }
+       }
 
        PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate,
                                                                                  CurrentMemoryContext));
 {
        ArrayType  *v = PG_GETARG_ARRAYTYPE_P(0);
        char       *fldsep = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+       PG_RETURN_TEXT_P(array_to_text_internal(fcinfo, v, fldsep, NULL));
+}
+
+/*
+ * array_to_text_null
+ * concatenate Cstring representation of input array elements
+ * using provided field separator and null string
+ *
+ * This version is not strict so we have to test for null inputs explicitly.
+ */
+Datum
+array_to_text_null(PG_FUNCTION_ARGS)
+{
+       ArrayType  *v;
+       char       *fldsep;
+       char       *null_string;
+
+       /* returns NULL when first or second parameter is NULL */
+       if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
+               PG_RETURN_NULL();
+       
+       v = PG_GETARG_ARRAYTYPE_P(0);
+       fldsep = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+       /* NULL null string is passed through as a null pointer */
+       if (!PG_ARGISNULL(2))
+               null_string = text_to_cstring(PG_GETARG_TEXT_PP(2));
+       else
+               null_string = NULL;
+
+       PG_RETURN_TEXT_P(array_to_text_internal(fcinfo, v, fldsep, null_string));
+}
+
+/*
+ * common code for array_to_text and array_to_text_null functions
+ */
+static text *
+array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
+                                          char *fldsep, char *null_string)
+{
+       text       *result;
        int                     nitems,
                           *dims,
                                ndims;
 
        /* if there are no elements, return an empty string */
        if (nitems == 0)
-               PG_RETURN_TEXT_P(cstring_to_text(""));
+               return cstring_to_text_with_len("", 0);
 
        element_type = ARR_ELEMTYPE(v);
        initStringInfo(&buf);
                /* Get source element, checking for NULL */
                if (bitmap && (*bitmap & bitmask) == 0)
                {
-                       /* we ignore nulls */
+                       /* if null_string is NULL, we just ignore null elements */
+                       if (null_string != NULL)
+                       {
+                               if (printed)
+                                       appendStringInfo(&buf, "%s%s", fldsep, null_string);
+                               else
+                                       appendStringInfoString(&buf, null_string);
+                               printed = true;
+                       }
                }
                else
                {
                        }
                }
        }
+       
+       result = cstring_to_text_with_len(buf.data, buf.len);
+       pfree(buf.data);
 
-       PG_RETURN_TEXT_P(cstring_to_text_with_len(buf.data, buf.len));
+       return result;
 }
 
 #define HEXBASE 16
 
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.593 2010/08/08 19:15:27 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.594 2010/08/10 21:51:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201008081
+#define CATALOG_VERSION_NO     201008101
 
 #endif
 
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.577 2010/08/08 19:15:27 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.578 2010/08/10 21:51:00 tgl Exp $
  *
  * NOTES
  *       The script catalog/genbki.pl reads this file and generates .bki
 DESCR("prepend element onto front of array");
 DATA(insert OID = 383 (  array_cat                PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_cat _null_ _null_ _null_ ));
 DESCR("concatenate two arrays");
-DATA(insert OID = 394 (  string_to_array   PGNSP PGUID 12 1 0 0 f f f t f i 2 0 1009 "25 25" _null_ _null_ _null_ _null_ text_to_array _null_ _null_ _null_ ));
+DATA(insert OID = 394 (  string_to_array   PGNSP PGUID 12 1 0 0 f f f f f i 2 0 1009 "25 25" _null_ _null_ _null_ _null_ text_to_array _null_ _null_ _null_ ));
 DESCR("split delimited text into text[]");
 DATA(insert OID = 395 (  array_to_string   PGNSP PGUID 12 1 0 0 f f f t f s 2 0 25 "2277 25" _null_ _null_ _null_ _null_ array_to_text _null_ _null_ _null_ ));
 DESCR("concatenate array elements, using delimiter, into text");
+DATA(insert OID = 376 (  string_to_array   PGNSP PGUID 12 1 0 0 f f f f f i 3 0 1009 "25 25 25" _null_ _null_ _null_ _null_ text_to_array_null _null_ _null_ _null_ ));
+DESCR("split delimited text into text[], with null string");
+DATA(insert OID = 384 (  array_to_string   PGNSP PGUID 12 1 0 0 f f f f f s 3 0 25 "2277 25 25" _null_ _null_ _null_ _null_ array_to_text_null _null_ _null_ _null_ ));
+DESCR("concatenate array elements, using delimiter and null string, into text");
 DATA(insert OID = 515 (  array_larger     PGNSP PGUID 12 1 0 0 f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_larger _null_ _null_ _null_ ));
 DESCR("larger of two");
 DATA(insert OID = 516 (  array_smaller    PGNSP PGUID 12 1 0 0 f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_smaller _null_ _null_ _null_ ));
 
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/array.h,v 1.77 2010/01/02 16:58:09 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/array.h,v 1.78 2010/08/10 21:51:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 extern ArrayType *create_singleton_array(FunctionCallInfo fcinfo,
                                           Oid element_type,
                                           Datum element,
+                                          bool isNull,
                                           int ndims);
 
 extern Datum array_agg_transfn(PG_FUNCTION_ARGS);
 
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.353 2010/08/05 18:21:19 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.354 2010/08/10 21:51:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 extern Datum split_text(PG_FUNCTION_ARGS);
 extern Datum text_to_array(PG_FUNCTION_ARGS);
 extern Datum array_to_text(PG_FUNCTION_ARGS);
+extern Datum text_to_array_null(PG_FUNCTION_ARGS);
+extern Datum array_to_text_null(PG_FUNCTION_ARGS);
 extern Datum to_hex32(PG_FUNCTION_ARGS);
 extern Datum to_hex64(PG_FUNCTION_ARGS);
 extern Datum md5_text(PG_FUNCTION_ARGS);
 
 select string_to_array('', '|');
  string_to_array 
 -----------------
- 
+ {}
 (1 row)
 
 select string_to_array('1|2|3', NULL);
  string_to_array 
 -----------------
- 
+ {1,|,2,|,3}
+(1 row)
+
+select string_to_array(NULL, '|') IS NULL;
+ ?column? 
+----------
+ t
 (1 row)
 
-select string_to_array(NULL, '|');
+select string_to_array('abc', '');
  string_to_array 
 -----------------
+ {abc}
+(1 row)
+
+select string_to_array('abc', '', 'abc');
+ string_to_array 
+-----------------
+ {NULL}
+(1 row)
+
+select string_to_array('abc', ',');
+ string_to_array 
+-----------------
+ {abc}
+(1 row)
+
+select string_to_array('abc', ',', 'abc');
+ string_to_array 
+-----------------
+ {NULL}
+(1 row)
+
+select string_to_array('1,2,3,4,,6', ',');
+ string_to_array 
+-----------------
+ {1,2,3,4,"",6}
+(1 row)
+
+select string_to_array('1,2,3,4,,6', ',', '');
+ string_to_array  
+------------------
+ {1,2,3,4,NULL,6}
+(1 row)
+
+select string_to_array('1,2,3,4,*,6', ',', '*');
+ string_to_array  
+------------------
+ {1,2,3,4,NULL,6}
+(1 row)
+
+select array_to_string(NULL::int4[], ',') IS NULL;
+ ?column? 
+----------
+ t
+(1 row)
+
+select array_to_string('{}'::int4[], ',');
+ array_to_string 
+-----------------
  
 (1 row)
 
+select array_to_string(array[1,2,3,4,NULL,6], ',');
+ array_to_string 
+-----------------
+ 1,2,3,4,6
+(1 row)
+
+select array_to_string(array[1,2,3,4,NULL,6], ',', '*');
+ array_to_string 
+-----------------
+ 1,2,3,4,*,6
+(1 row)
+
+select array_to_string(array[1,2,3,4,NULL,6], NULL);
+ array_to_string 
+-----------------
+ 
+(1 row)
+
+select array_to_string(array[1,2,3,4,NULL,6], ',', NULL);
+ array_to_string 
+-----------------
+ 1,2,3,4,6
+(1 row)
+
 select array_to_string(string_to_array('1|2|3', '|'), '|');
  array_to_string 
 -----------------
 
 select string_to_array('1|2|3', '');
 select string_to_array('', '|');
 select string_to_array('1|2|3', NULL);
-select string_to_array(NULL, '|');
+select string_to_array(NULL, '|') IS NULL;
+select string_to_array('abc', '');
+select string_to_array('abc', '', 'abc');
+select string_to_array('abc', ',');
+select string_to_array('abc', ',', 'abc');
+select string_to_array('1,2,3,4,,6', ',');
+select string_to_array('1,2,3,4,,6', ',', '');
+select string_to_array('1,2,3,4,*,6', ',', '*');
+
+select array_to_string(NULL::int4[], ',') IS NULL;
+select array_to_string('{}'::int4[], ',');
+select array_to_string(array[1,2,3,4,NULL,6], ',');
+select array_to_string(array[1,2,3,4,NULL,6], ',', '*');
+select array_to_string(array[1,2,3,4,NULL,6], NULL);
+select array_to_string(array[1,2,3,4,NULL,6], ',', NULL);
 
 select array_to_string(string_to_array('1|2|3', '|'), '|');