*** a/doc/src/sgml/func.sgml --- b/doc/src/sgml/func.sgml *************** *** 2034,2039 **** --- 2034,2049 ---- + Functions concat, concat_ws and + format are variadic - variadic arguments should be passed + as usual or as array that is labeled as VARIADIC. + Functions concat and concat_ws + returns NULL, when variadic array argument is NULL and empty string + when variadic array argument is empty array. Result of format + primary depends on content of fmt string. + + + See also the aggregate function string_agg in . *** a/src/backend/utils/adt/varlena.c --- b/src/backend/utils/adt/varlena.c *************** *** 76,83 **** 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 Datum text_to_array_internal(PG_FUNCTION_ARGS); static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v, --- 76,83 ---- bool length_not_specified); static bytea *bytea_overlay(bytea *t1, bytea *t2, int sp, int sl); static StringInfo makeStringAggState(FunctionCallInfo fcinfo); ! static void text_format_string_conversion(StringInfo buf, char conversion, ! Oid typOutput, Datum value, bool isNull); static Datum text_to_array_internal(PG_FUNCTION_ARGS); static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v, *************** *** 3792,3839 **** 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). */ static text * ! concat_internal(const char *sepstr, int seplen, int argidx, FunctionCallInfo fcinfo) { ! text *result; ! StringInfoData str; ! bool first_arg = true; ! int i; ! initStringInfo(&str); ! for (i = argidx; i < PG_NARGS(); i++) { ! if (!PG_ARGISNULL(i)) { ! Datum value = PG_GETARG_DATUM(i); ! Oid valtype; ! Oid typOutput; ! bool typIsVarlena; ! ! /* add separator if appropriate */ ! if (first_arg) ! first_arg = false; ! else ! appendBinaryStringInfo(&str, sepstr, seplen); ! ! /* call the appropriate type output function, append the result */ ! valtype = get_fn_expr_argtype(fcinfo->flinfo, i); ! if (!OidIsValid(valtype)) ! elog(ERROR, "could not determine data type of concat() input"); ! getTypeOutputInfo(valtype, &typOutput, &typIsVarlena); ! appendStringInfoString(&str, ! OidOutputFunctionCall(typOutput, value)); } - } ! result = cstring_to_text_with_len(str.data, str.len); ! pfree(str.data); ! return result; } /* --- 3792,3877 ---- * Implementation of both concat() and concat_ws(). * * sepstr/seplen describe the separator. argidx is the first argument ! * to concatenate (counting from zero). When argument with variadic ! * array is NULL, then returns NULL, else string or empty string. */ static text * ! concat_internal(char *sepstr, int argidx, FunctionCallInfo fcinfo) { ! /* ! * When this routine is called from variadic function with ! * labeled VARIADIC parameter, then sematic is very close to ! * array_to_string function - and we can use code ! * array_to_text_internal, that is well optimized. ! */ ! if (get_fn_expr_variadic(fcinfo->flinfo)) ! { ! Oid arr_typid; ! ArrayType *arr; ! /* do nothing, when argument is NULL */ ! if (fcinfo->argnull[fcinfo->nargs - 1]) ! return NULL; ! ! /* sanity check - any array type is requested */ ! arr_typid = get_fn_expr_argtype(fcinfo->flinfo, fcinfo->nargs - 1); ! if (!OidIsValid(arr_typid)) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_PARAMETER_VALUE), ! errmsg("could not determinate variadic argument data type"))); ! ! if (!OidIsValid(get_element_type(arr_typid))) ! ereport(ERROR, ! (errcode(ERRCODE_DATATYPE_MISMATCH), ! errmsg("variadic argument should be an array"))); ! /* now is safe get the array */ ! arr = DatumGetArrayTypeP(fcinfo->arg[fcinfo->nargs - 1]); ! ! /* serialize an array */ ! return array_to_text_internal(fcinfo, arr, sepstr, NULL); ! } ! else { ! text *result; ! StringInfoData str; ! bool first_arg = true; ! int i; ! ! /* process variadic "any" parameters stored as FunctionCallInfo arguments */ ! initStringInfo(&str); ! ! for (i = argidx; i < PG_NARGS(); i++) { ! if (!PG_ARGISNULL(i)) ! { ! Datum value = PG_GETARG_DATUM(i); ! Oid valtype; ! Oid typOutput; ! bool typIsVarlena; ! ! /* add separator if appropriate */ ! if (first_arg) ! first_arg = false; ! else ! appendStringInfoString(&str, sepstr); ! ! /* call the appropriate type output function, append the result */ ! valtype = get_fn_expr_argtype(fcinfo->flinfo, i); ! if (!OidIsValid(valtype)) ! elog(ERROR, "could not determine data type of concat() input"); ! getTypeOutputInfo(valtype, &typOutput, &typIsVarlena); ! appendStringInfoString(&str, ! OidOutputFunctionCall(typOutput, value)); ! } } ! result = cstring_to_text_with_len(str.data, str.len); ! pfree(str.data); ! return result; ! } } /* *************** *** 3842,3848 **** concat_internal(const char *sepstr, int seplen, int argidx, Datum text_concat(PG_FUNCTION_ARGS) { ! PG_RETURN_TEXT_P(concat_internal("", 0, 0, fcinfo)); } /* --- 3880,3892 ---- Datum text_concat(PG_FUNCTION_ARGS) { ! text *result; ! ! result = concat_internal("", 0, fcinfo); ! if (result == NULL) ! PG_RETURN_NULL(); ! ! PG_RETURN_TEXT_P(result); } /* *************** *** 3852,3867 **** text_concat(PG_FUNCTION_ARGS) Datum text_concat_ws(PG_FUNCTION_ARGS) { ! text *sep; /* return NULL when separator is NULL */ if (PG_ARGISNULL(0)) PG_RETURN_NULL(); ! sep = PG_GETARG_TEXT_PP(0); ! PG_RETURN_TEXT_P(concat_internal(VARDATA_ANY(sep), VARSIZE_ANY_EXHDR(sep), ! 1, fcinfo)); } /* --- 3896,3915 ---- Datum text_concat_ws(PG_FUNCTION_ARGS) { ! 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)); ! result = concat_internal(sep, 1, fcinfo); ! if (result == NULL) ! PG_RETURN_NULL(); ! ! PG_RETURN_TEXT_P(result); } /* *************** *** 3959,3969 **** text_format(PG_FUNCTION_ARGS) --- 4007,4079 ---- 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; + Oid typoutputfunc = InvalidOid; /* When format string is null, returns null */ if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + /* when last argument was marked as VARIADIC sanitize variadic argument */ + if (get_fn_expr_variadic(fcinfo->flinfo)) + { + Oid array_type; + ArrayType *arr; + int16 typlen; + bool typbyval; + char typalign; + char typdelim; + Oid typioparam; + int nitems = 0; + + /* + * Variadic argument should be NULL in this case, becase there should not + * be any reference from fmt string. + */ + if (!PG_ARGISNULL(1)) + { + /* sanity check - any array type is requested */ + array_type = get_fn_expr_argtype(fcinfo->flinfo, 1); + if (!OidIsValid(array_type)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determinate variadic argument data type"))); + + if (!OidIsValid(get_element_type(array_type))) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("variadic argument should be an array"))); + + /* now we know so variadic "any" argument is really array */ + arr = PG_GETARG_ARRAYTYPE_P(1); + element_type = ARR_ELEMTYPE(arr); + + get_type_io_data(element_type, IOFunc_output, + &typlen, &typbyval, &typalign, + &typdelim, &typioparam, &typoutputfunc); + + /* we know typoutput function, so we can set prev_typid */ + prev_type = element_type; + + deconstruct_array(arr, element_type, typlen, typbyval, + typalign, &elements, &nulls, + &nitems); + } + + nargs = nitems + 1; + funcvariadic = true; + } + else + { + nargs = PG_NARGS(); + funcvariadic = false; + } + /* Setup for main loop. */ fmt = PG_GETARG_TEXT_PP(0); start_ptr = VARDATA_ANY(fmt); *************** *** 4062,4068 **** text_format(PG_FUNCTION_ARGS) } /* Not enough arguments? Deduct 1 to avoid counting format string. */ ! if (arg > PG_NARGS() - 1) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("too few arguments for format"))); --- 4172,4178 ---- } /* Not enough arguments? Deduct 1 to avoid counting format string. */ ! if (arg > nargs - 1) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("too few arguments for format"))); *************** *** 4072,4087 **** text_format(PG_FUNCTION_ARGS) * 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); break; default: ereport(ERROR, --- 4182,4215 ---- * or not an argument position was present, it's known that at least * one character remains in the string at this point. */ ! 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; ! } ! ! /* try to reuse typOutput function */ ! if (typoutputfunc == InvalidOid || prev_type != typid) ! { ! bool typIsVarlena; ! ! getTypeOutputInfo(typid, &typoutputfunc, &typIsVarlena); ! prev_type = typid; ! } switch (*cp) { case 's': case 'I': case 'L': ! text_format_string_conversion(&str, *cp, typoutputfunc, value, isNull); break; default: ereport(ERROR, *************** *** 4095,4110 **** text_format(PG_FUNCTION_ARGS) result = cstring_to_text_with_len(str.data, str.len); pfree(str.data); PG_RETURN_TEXT_P(result); } /* Format a %s, %I, or %L conversion. */ ! void text_format_string_conversion(StringInfo buf, char conversion, ! Oid typid, Datum value, bool isNull) { - Oid typOutput; - bool typIsVarlena; char *str; /* Handle NULL arguments before trying to stringify the value. */ --- 4223,4241 ---- result = cstring_to_text_with_len(str.data, str.len); pfree(str.data); + if (elements != NULL) + pfree(elements); + if (nulls != NULL) + pfree(nulls); + PG_RETURN_TEXT_P(result); } /* Format a %s, %I, or %L conversion. */ ! static void text_format_string_conversion(StringInfo buf, char conversion, ! Oid typOutput, Datum value, bool isNull) { char *str; /* Handle NULL arguments before trying to stringify the value. */ *************** *** 4120,4126 **** text_format_string_conversion(StringInfo buf, char conversion, } /* Stringify. */ - getTypeOutputInfo(typid, &typOutput, &typIsVarlena); str = OidOutputFunctionCall(typOutput, value); /* Escape. */ --- 4251,4256 ---- *** a/src/test/regress/expected/text.out --- b/src/test/regress/expected/text.out *************** *** 136,141 **** select quote_literal(e'\\'); --- 136,175 ---- 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 should be an array /* * format */ *************** *** 241,243 **** select format('Hello %s %s, %2$s %2$s', 'World', 'Hello again'); --- 275,330 ---- Hello World Hello again, Hello again Hello again (1 row) + -- check pass variadic labeled argument + select format('%s, %s', variadic array['Hello','World']); + format + -------------- + Hello, World + (1 row) + + -- check others datatypes + 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 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) + + -- check bypassing FUNC_MAX_ARGS limit via variadic labeled argument + select right(format(string_agg('%s',','), variadic array_agg(i)),31) from generate_series(1,500) g(i); + right + --------------------------------- + 493,494,495,496,497,498,499,500 + (1 row) + *** a/src/test/regress/sql/text.sql --- b/src/test/regress/sql/text.sql *************** *** 44,49 **** select i, left('ahoj', i), right('ahoj', i) from generate_series(-5, 5) t(i) ord --- 44,57 ---- 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 *************** *** 76,78 **** select format('%1$1', 1); --- 84,103 ---- --checkk 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 pass variadic labeled argument + select format('%s, %s', variadic array['Hello','World']); + -- check others datatypes + 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 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); + + -- check bypassing FUNC_MAX_ARGS limit via variadic labeled argument + select right(format(string_agg('%s',','), variadic array_agg(i)),31) from generate_series(1,500) g(i);