</para>
<para>
Splits <parameter>string</parameter> using a POSIX regular
- expression as the delimiter; see
+ expression as the delimiter, producing an array of results; see
<xref linkend="functions-posix-regexp"/>.
</para>
<para>
</para>
<para>
Splits <parameter>string</parameter> using a POSIX regular
- expression as the delimiter; see
+ expression as the delimiter, producing a set of results; see
<xref linkend="functions-posix-regexp"/>.
</para>
<para>
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>string_to_array</primary>
+ </indexterm>
+ <function>string_to_array</function> ( <parameter>string</parameter> <type>text</type>, <parameter>delimiter</parameter> <type>text</type> <optional>, <parameter>null_string</parameter> <type>text</type> </optional> )
+ <returnvalue>text[]</returnvalue>
+ </para>
+ <para>
+ Splits the <parameter>string</parameter> at occurrences
+ of <parameter>delimiter</parameter> and forms the resulting fields
+ into a <type>text</type> array.
+ If <parameter>delimiter</parameter> is <literal>NULL</literal>,
+ each character in the <parameter>string</parameter> will become a
+ separate element in the array.
+ If <parameter>delimiter</parameter> is an empty string, then
+ the <parameter>string</parameter> is treated as a single field.
+ If <parameter>null_string</parameter> is supplied and is
+ not <literal>NULL</literal>, fields matching that string are
+ replaced by <literal>NULL</literal>.
+ </para>
+ <para>
+ <literal>string_to_array('xx~~yy~~zz', '~~', 'yy')</literal>
+ <returnvalue>{xx,NULL,zz}</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>string_to_table</primary>
+ </indexterm>
+ <function>string_to_table</function> ( <parameter>string</parameter> <type>text</type>, <parameter>delimiter</parameter> <type>text</type> <optional>, <parameter>null_string</parameter> <type>text</type> </optional> )
+ <returnvalue>setof text</returnvalue>
+ </para>
+ <para>
+ Splits the <parameter>string</parameter> at occurrences
+ of <parameter>delimiter</parameter> and returns the resulting fields
+ as a set of <type>text</type> rows.
+ If <parameter>delimiter</parameter> is <literal>NULL</literal>,
+ each character in the <parameter>string</parameter> will become a
+ separate row of the result.
+ If <parameter>delimiter</parameter> is an empty string, then
+ the <parameter>string</parameter> is treated as a single field.
+ If <parameter>null_string</parameter> is supplied and is
+ not <literal>NULL</literal>, fields matching that string are
+ replaced by <literal>NULL</literal>.
+ </para>
+ <para>
+ <literal>string_to_table('xx~^~yy~^~zz', '~^~', 'yy')</literal>
+ <returnvalue></returnvalue>
+<programlisting>
+ xx
+ NULL
+ zz
+</programlisting>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
</para></entry>
</row>
- <row>
- <entry role="func_table_entry"><para role="func_signature">
- <indexterm>
- <primary>string_to_array</primary>
- </indexterm>
- <function>string_to_array</function> ( <parameter>string</parameter> <type>text</type>, <parameter>delimiter</parameter> <type>text</type> <optional>, <parameter>null_string</parameter> <type>text</type> </optional> )
- <returnvalue>text[]</returnvalue>
- </para>
- <para>
- Splits the <parameter>string</parameter> at occurrences
- of <parameter>delimiter</parameter> and forms the remaining data
- into a <type>text</type> array.
- If <parameter>delimiter</parameter> is <literal>NULL</literal>,
- each character in the <parameter>string</parameter> will become a
- separate element in the array.
- If <parameter>delimiter</parameter> is an empty string, then
- the <parameter>string</parameter> is treated as a single field.
- If <parameter>null_string</parameter> is supplied and is
- not <literal>NULL</literal>, fields matching that string are converted
- to <literal>NULL</literal> entries.
- </para>
- <para>
- <literal>string_to_array('xx~~yy~~zz', '~~', 'yy')</literal>
- <returnvalue>{xx,NULL,zz}</returnvalue>
- </para></entry>
- </row>
-
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
#include "lib/hyperloglog.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
+#include "nodes/execnodes.h"
#include "parser/scansup.h"
#include "port/pg_bswap.h"
#include "regex/regex.h"
pg_locale_t locale;
} VarStringSortSupport;
+/*
+ * Output data for split_text(): we output either to an array or a table.
+ * tupstore and tupdesc must be set up in advance to output to a table.
+ */
+typedef struct
+{
+ ArrayBuildState *astate;
+ Tuplestorestate *tupstore;
+ TupleDesc tupdesc;
+} SplitTextOutputData;
+
/*
* This should be large enough that most strings will fit, but small enough
* that we feel comfortable putting it on the stack
bool length_not_specified);
static bytea *bytea_overlay(bytea *t1, bytea *t2, int sp, int sl);
static void appendStringInfoText(StringInfo str, const text *t);
-static Datum text_to_array_internal(PG_FUNCTION_ARGS);
+static bool split_text(FunctionCallInfo fcinfo, SplitTextOutputData *tstate);
+static void split_text_accum_result(SplitTextOutputData *tstate,
+ text *field_value,
+ text *null_string,
+ Oid collation);
static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
const char *fldsep, const char *null_string);
static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
}
/*
- * split_text
+ * split_part
* parse input string
* return ord item (1 based)
* based on provided field separator
*/
Datum
-split_text(PG_FUNCTION_ARGS)
+split_part(PG_FUNCTION_ARGS)
{
text *inputstring = PG_GETARG_TEXT_PP(0);
text *fldsep = PG_GETARG_TEXT_PP(1);
/* empty field separator */
if (fldsep_len < 1)
{
- text_position_cleanup(&state);
/* if first field, return input string, else empty string */
if (fldnum == 1)
PG_RETURN_TEXT_P(inputstring);
Datum
text_to_array(PG_FUNCTION_ARGS)
{
- return text_to_array_internal(fcinfo);
+ SplitTextOutputData tstate;
+
+ /* For array output, tstate should start as all zeroes */
+ memset(&tstate, 0, sizeof(tstate));
+
+ if (!split_text(fcinfo, &tstate))
+ PG_RETURN_NULL();
+
+ if (tstate.astate == NULL)
+ PG_RETURN_ARRAYTYPE_P(construct_empty_array(TEXTOID));
+
+ PG_RETURN_ARRAYTYPE_P(makeArrayResult(tstate.astate,
+ CurrentMemoryContext));
}
/*
Datum
text_to_array_null(PG_FUNCTION_ARGS)
{
- return text_to_array_internal(fcinfo);
+ return text_to_array(fcinfo);
+}
+
+/*
+ * text_to_table
+ * parse input string and return table of elements,
+ * based on provided field separator
+ */
+Datum
+text_to_table(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+ SplitTextOutputData tstate;
+ MemoryContext old_cxt;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsi == NULL || !IsA(rsi, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsi->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* OK, prepare tuplestore in per-query memory */
+ old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+ tstate.astate = NULL;
+ tstate.tupdesc = CreateTupleDescCopy(rsi->expectedDesc);
+ tstate.tupstore = tuplestore_begin_heap(true, false, work_mem);
+
+ MemoryContextSwitchTo(old_cxt);
+
+ (void) split_text(fcinfo, &tstate);
+
+ tuplestore_donestoring(tstate.tupstore);
+
+ rsi->returnMode = SFRM_Materialize;
+ rsi->setResult = tstate.tupstore;
+ rsi->setDesc = tstate.tupdesc;
+
+ return (Datum) 0;
+}
+
+/*
+ * text_to_table_null
+ * parse input string and return table 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_table_null(PG_FUNCTION_ARGS)
+{
+ return text_to_table(fcinfo);
}
/*
- * common code for text_to_array and text_to_array_null functions
+ * Common code for text_to_array, text_to_array_null, text_to_table
+ * and text_to_table_null functions.
*
* These are not strict so we have to test for null inputs explicitly.
+ * Returns false if result is to be null, else returns true.
+ *
+ * Note that if the result is valid but empty (zero elements), we return
+ * without changing *tstate --- caller must handle that case, too.
*/
-static Datum
-text_to_array_internal(PG_FUNCTION_ARGS)
+static bool
+split_text(FunctionCallInfo fcinfo, SplitTextOutputData *tstate)
{
text *inputstring;
text *fldsep;
text *null_string;
+ Oid collation = PG_GET_COLLATION();
int inputstring_len;
int fldsep_len;
char *start_ptr;
text *result_text;
- bool is_null;
- ArrayBuildState *astate = NULL;
/* when input string is NULL, then result is NULL too */
if (PG_ARGISNULL(0))
- PG_RETURN_NULL();
+ return false;
inputstring = PG_GETARG_TEXT_PP(0);
inputstring_len = VARSIZE_ANY_EXHDR(inputstring);
fldsep_len = VARSIZE_ANY_EXHDR(fldsep);
- /* return empty array for empty input string */
+ /* return empty set for empty input string */
if (inputstring_len < 1)
- PG_RETURN_ARRAYTYPE_P(construct_empty_array(TEXTOID));
+ return true;
- /*
- * empty field separator: return the input string as a one-element
- * array
- */
+ /* empty field separator: return input string as a one-element set */
if (fldsep_len < 1)
{
- Datum elems[1];
- bool nulls[1];
- int dims[1];
- int lbs[1];
-
- /* single element can be a NULL too */
- is_null = null_string ? text_isequal(inputstring, null_string, PG_GET_COLLATION()) : false;
-
- elems[0] = PointerGetDatum(inputstring);
- nulls[0] = is_null;
- dims[0] = 1;
- lbs[0] = 1;
- /* XXX: this hardcodes assumptions about the text type */
- PG_RETURN_ARRAYTYPE_P(construct_md_array(elems, nulls,
- 1, dims, lbs,
- TEXTOID, -1, false, TYPALIGN_INT));
+ split_text_accum_result(tstate, inputstring,
+ null_string, collation);
+ return true;
}
- text_position_setup(inputstring, fldsep, PG_GET_COLLATION(), &state);
+ text_position_setup(inputstring, fldsep, collation, &state);
start_ptr = VARDATA_ANY(inputstring);
chunk_len = end_ptr - start_ptr;
}
- /* must build a temp text datum to pass to accumArrayResult */
+ /* build a temp text datum to pass to split_text_accum_result */
result_text = cstring_to_text_with_len(start_ptr, chunk_len);
- is_null = null_string ? text_isequal(result_text, null_string, PG_GET_COLLATION()) : false;
/* stash away this field */
- astate = accumArrayResult(astate,
- PointerGetDatum(result_text),
- is_null,
- TEXTOID,
- CurrentMemoryContext);
+ split_text_accum_result(tstate, result_text,
+ null_string, collation);
pfree(result_text);
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.
+ * When fldsep is NULL, each character in the input string becomes a
+ * separate element in the result set. 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)
CHECK_FOR_INTERRUPTS();
- /* must build a temp text datum to pass to accumArrayResult */
+ /* build a temp text datum to pass to split_text_accum_result */
result_text = cstring_to_text_with_len(start_ptr, chunk_len);
- is_null = null_string ? text_isequal(result_text, null_string, PG_GET_COLLATION()) : false;
/* stash away this field */
- astate = accumArrayResult(astate,
- PointerGetDatum(result_text),
- is_null,
- TEXTOID,
- CurrentMemoryContext);
+ split_text_accum_result(tstate, result_text,
+ null_string, collation);
pfree(result_text);
}
}
- PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate,
- CurrentMemoryContext));
+ return true;
+}
+
+/*
+ * Add text item to result set (table or array).
+ *
+ * This is also responsible for checking to see if the item matches
+ * the null_string, in which case we should emit NULL instead.
+ */
+static void
+split_text_accum_result(SplitTextOutputData *tstate,
+ text *field_value,
+ text *null_string,
+ Oid collation)
+{
+ bool is_null = false;
+
+ if (null_string && text_isequal(field_value, null_string, collation))
+ is_null = true;
+
+ if (tstate->tupstore)
+ {
+ Datum values[1];
+ bool nulls[1];
+
+ values[0] = PointerGetDatum(field_value);
+ nulls[0] = is_null;
+
+ tuplestore_putvalues(tstate->tupstore,
+ tstate->tupdesc,
+ values,
+ nulls);
+ }
+ else
+ {
+ tstate->astate = accumArrayResult(tstate->astate,
+ PointerGetDatum(field_value),
+ is_null,
+ TEXTOID,
+ CurrentMemoryContext);
+ }
}
/*
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202008301
+#define CATALOG_VERSION_NO 202009021
#endif
{ oid => '383',
proname => 'array_cat', proisstrict => 'f', prorettype => 'anyarray',
proargtypes => 'anyarray anyarray', prosrc => 'array_cat' },
-{ oid => '394', descr => 'split delimited text into text[]',
+{ oid => '394', descr => 'split delimited text',
proname => 'string_to_array', proisstrict => 'f', prorettype => '_text',
proargtypes => 'text text', prosrc => 'text_to_array' },
+{ oid => '376', descr => 'split delimited text, with null string',
+ proname => 'string_to_array', proisstrict => 'f', prorettype => '_text',
+ proargtypes => 'text text text', prosrc => 'text_to_array_null' },
+{ oid => '8432', descr => 'split delimited text',
+ proname => 'string_to_table', proisstrict => 'f', prorows => '1000',
+ proretset => 't', prorettype => 'text', proargtypes => 'text text',
+ prosrc => 'text_to_table' },
+{ oid => '8433', descr => 'split delimited text, with null string',
+ proname => 'string_to_table', proisstrict => 'f', prorows => '1000',
+ proretset => 't', prorettype => 'text', proargtypes => 'text text text',
+ prosrc => 'text_to_table_null' },
{ oid => '395',
descr => 'concatenate array elements, using delimiter, into text',
proname => 'array_to_string', provolatile => 's', prorettype => 'text',
proargtypes => 'anyarray text', prosrc => 'array_to_text' },
-{ oid => '376', descr => 'split delimited text into text[], with null string',
- proname => 'string_to_array', proisstrict => 'f', prorettype => '_text',
- proargtypes => 'text text text', prosrc => 'text_to_array_null' },
{ oid => '384',
descr => 'concatenate array elements, using delimiter and null string, into text',
proname => 'array_to_string', proisstrict => 'f', provolatile => 's',
prosrc => 'regexp_matches' },
{ oid => '2088', descr => 'split string by field_sep and return field_num',
proname => 'split_part', prorettype => 'text',
- proargtypes => 'text text int4', prosrc => 'split_text' },
+ proargtypes => 'text text int4', prosrc => 'split_part' },
{ oid => '2765', descr => 'split string by pattern',
proname => 'regexp_split_to_table', prorows => '1000', proretset => 't',
prorettype => 'text', proargtypes => 'text text',
{1,2,3,4,NULL,6}
(1 row)
+select v, v is null as "is null" from string_to_table('1|2|3', '|') g(v);
+ v | is null
+---+---------
+ 1 | f
+ 2 | f
+ 3 | f
+(3 rows)
+
+select v, v is null as "is null" from string_to_table('1|2|3|', '|') g(v);
+ v | is null
+---+---------
+ 1 | f
+ 2 | f
+ 3 | f
+ | f
+(4 rows)
+
+select v, v is null as "is null" from string_to_table('1||2|3||', '||') g(v);
+ v | is null
+-----+---------
+ 1 | f
+ 2|3 | f
+ | f
+(3 rows)
+
+select v, v is null as "is null" from string_to_table('1|2|3', '') g(v);
+ v | is null
+-------+---------
+ 1|2|3 | f
+(1 row)
+
+select v, v is null as "is null" from string_to_table('', '|') g(v);
+ v | is null
+---+---------
+(0 rows)
+
+select v, v is null as "is null" from string_to_table('1|2|3', NULL) g(v);
+ v | is null
+---+---------
+ 1 | f
+ | | f
+ 2 | f
+ | | f
+ 3 | f
+(5 rows)
+
+select v, v is null as "is null" from string_to_table(NULL, '|') g(v);
+ v | is null
+---+---------
+(0 rows)
+
+select v, v is null as "is null" from string_to_table('abc', '') g(v);
+ v | is null
+-----+---------
+ abc | f
+(1 row)
+
+select v, v is null as "is null" from string_to_table('abc', '', 'abc') g(v);
+ v | is null
+---+---------
+ | t
+(1 row)
+
+select v, v is null as "is null" from string_to_table('abc', ',') g(v);
+ v | is null
+-----+---------
+ abc | f
+(1 row)
+
+select v, v is null as "is null" from string_to_table('abc', ',', 'abc') g(v);
+ v | is null
+---+---------
+ | t
+(1 row)
+
+select v, v is null as "is null" from string_to_table('1,2,3,4,,6', ',') g(v);
+ v | is null
+---+---------
+ 1 | f
+ 2 | f
+ 3 | f
+ 4 | f
+ | f
+ 6 | f
+(6 rows)
+
+select v, v is null as "is null" from string_to_table('1,2,3,4,,6', ',', '') g(v);
+ v | is null
+---+---------
+ 1 | f
+ 2 | f
+ 3 | f
+ 4 | f
+ | t
+ 6 | f
+(6 rows)
+
+select v, v is null as "is null" from string_to_table('1,2,3,4,*,6', ',', '*') g(v);
+ v | is null
+---+---------
+ 1 | f
+ 2 | f
+ 3 | f
+ 4 | f
+ | t
+ 6 | f
+(6 rows)
+
select array_to_string(NULL::int4[], ',') IS NULL;
?column?
----------
select string_to_array('1,2,3,4,,6', ',', '');
select string_to_array('1,2,3,4,*,6', ',', '*');
+select v, v is null as "is null" from string_to_table('1|2|3', '|') g(v);
+select v, v is null as "is null" from string_to_table('1|2|3|', '|') g(v);
+select v, v is null as "is null" from string_to_table('1||2|3||', '||') g(v);
+select v, v is null as "is null" from string_to_table('1|2|3', '') g(v);
+select v, v is null as "is null" from string_to_table('', '|') g(v);
+select v, v is null as "is null" from string_to_table('1|2|3', NULL) g(v);
+select v, v is null as "is null" from string_to_table(NULL, '|') g(v);
+select v, v is null as "is null" from string_to_table('abc', '') g(v);
+select v, v is null as "is null" from string_to_table('abc', '', 'abc') g(v);
+select v, v is null as "is null" from string_to_table('abc', ',') g(v);
+select v, v is null as "is null" from string_to_table('abc', ',', 'abc') g(v);
+select v, v is null as "is null" from string_to_table('1,2,3,4,,6', ',') g(v);
+select v, v is null as "is null" from string_to_table('1,2,3,4,,6', ',', '') g(v);
+select v, v is null as "is null" from string_to_table('1,2,3,4,*,6', ',', '*') g(v);
+
select array_to_string(NULL::int4[], ',') IS NULL;
select array_to_string('{}'::int4[], ',');
select array_to_string(array[1,2,3,4,NULL,6], ',');