*** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** *** 1194,1199 **** _copyFuncExpr(const FuncExpr *from) --- 1194,1200 ---- COPY_SCALAR_FIELD(funcid); COPY_SCALAR_FIELD(funcresulttype); COPY_SCALAR_FIELD(funcretset); + COPY_SCALAR_FIELD(funcvariadic); COPY_SCALAR_FIELD(funcformat); COPY_SCALAR_FIELD(funccollid); COPY_SCALAR_FIELD(inputcollid); *** a/src/backend/nodes/equalfuncs.c --- b/src/backend/nodes/equalfuncs.c *************** *** 239,244 **** _equalFuncExpr(const FuncExpr *a, const FuncExpr *b) --- 239,245 ---- COMPARE_SCALAR_FIELD(funcid); COMPARE_SCALAR_FIELD(funcresulttype); COMPARE_SCALAR_FIELD(funcretset); + COMPARE_SCALAR_FIELD(funcvariadic); COMPARE_COERCIONFORM_FIELD(funcformat); COMPARE_SCALAR_FIELD(funccollid); COMPARE_SCALAR_FIELD(inputcollid); *** a/src/backend/nodes/makefuncs.c --- b/src/backend/nodes/makefuncs.c *************** *** 461,466 **** makeFuncExpr(Oid funcid, Oid rettype, List *args, --- 461,467 ---- funcexpr->funcid = funcid; funcexpr->funcresulttype = rettype; funcexpr->funcretset = false; /* only allowed case here */ + funcexpr->funcvariadic = false; /* only allowed case here */ funcexpr->funcformat = fformat; funcexpr->funccollid = funccollid; funcexpr->inputcollid = inputcollid; *** a/src/backend/nodes/outfuncs.c --- b/src/backend/nodes/outfuncs.c *************** *** 1000,1005 **** _outFuncExpr(StringInfo str, const FuncExpr *node) --- 1000,1006 ---- WRITE_OID_FIELD(funcid); WRITE_OID_FIELD(funcresulttype); WRITE_BOOL_FIELD(funcretset); + WRITE_BOOL_FIELD(funcvariadic); WRITE_ENUM_FIELD(funcformat, CoercionForm); WRITE_OID_FIELD(funccollid); WRITE_OID_FIELD(inputcollid); *** a/src/backend/nodes/readfuncs.c --- b/src/backend/nodes/readfuncs.c *************** *** 537,542 **** _readFuncExpr(void) --- 537,543 ---- READ_OID_FIELD(funcid); READ_OID_FIELD(funcresulttype); READ_BOOL_FIELD(funcretset); + READ_BOOL_FIELD(funcvariadic); READ_ENUM_FIELD(funcformat, CoercionForm); READ_OID_FIELD(funccollid); READ_OID_FIELD(inputcollid); *** a/src/backend/optimizer/util/clauses.c --- b/src/backend/optimizer/util/clauses.c *************** *** 110,115 **** static Node *simplify_boolean_equality(Oid opno, List *args); --- 110,116 ---- static Expr *simplify_function(Oid funcid, Oid result_type, int32 result_typmod, Oid result_collid, Oid input_collid, List **args_p, + bool funcvariadic, bool process_args, bool allow_non_const, eval_const_expressions_context *context); static List *expand_function_arguments(List *args, Oid result_type, *************** *** 121,126 **** static void recheck_cast_function_args(List *args, Oid result_type, --- 122,128 ---- HeapTuple func_tuple); static Expr *evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, Oid result_collid, Oid input_collid, List *args, + bool funcvariadic, HeapTuple func_tuple, eval_const_expressions_context *context); static Expr *inline_function(Oid funcid, Oid result_type, Oid result_collid, *************** *** 2314,2319 **** eval_const_expressions_mutator(Node *node, --- 2316,2322 ---- expr->funccollid, expr->inputcollid, &args, + expr->funcvariadic, true, true, context); *************** *** 2330,2335 **** eval_const_expressions_mutator(Node *node, --- 2333,2339 ---- newexpr->funcid = expr->funcid; newexpr->funcresulttype = expr->funcresulttype; newexpr->funcretset = expr->funcretset; + newexpr->funcvariadic = expr->funcvariadic; newexpr->funcformat = expr->funcformat; newexpr->funccollid = expr->funccollid; newexpr->inputcollid = expr->inputcollid; *************** *** 2359,2364 **** eval_const_expressions_mutator(Node *node, --- 2363,2369 ---- expr->opcollid, expr->inputcollid, &args, + false, true, true, context); *************** *** 2464,2469 **** eval_const_expressions_mutator(Node *node, --- 2469,2475 ---- &args, false, false, + false, context); if (simple) /* successfully simplified it */ { *************** *** 2665,2670 **** eval_const_expressions_mutator(Node *node, --- 2671,2677 ---- InvalidOid, InvalidOid, &args, + false, true, true, context); *************** *** 2697,2702 **** eval_const_expressions_mutator(Node *node, --- 2704,2710 ---- InvalidOid, &args, false, + false, true, context); if (simple) /* successfully simplified input fn */ *************** *** 3565,3570 **** simplify_boolean_equality(Oid opno, List *args) --- 3573,3579 ---- static Expr * simplify_function(Oid funcid, Oid result_type, int32 result_typmod, Oid result_collid, Oid input_collid, List **args_p, + bool funcvariadic, bool process_args, bool allow_non_const, eval_const_expressions_context *context) { *************** *** 3610,3615 **** simplify_function(Oid funcid, Oid result_type, int32 result_typmod, --- 3619,3625 ---- newexpr = evaluate_function(funcid, result_type, result_typmod, result_collid, input_collid, args, + funcvariadic, func_tuple, context); if (!newexpr && allow_non_const && OidIsValid(func_form->protransform)) *************** *** 3625,3630 **** simplify_function(Oid funcid, Oid result_type, int32 result_typmod, --- 3635,3641 ---- fexpr.funcid = funcid; fexpr.funcresulttype = result_type; fexpr.funcretset = func_form->proretset; + fexpr.funcvariadic = funcvariadic; fexpr.funcformat = COERCE_EXPLICIT_CALL; fexpr.funccollid = result_collid; fexpr.inputcollid = input_collid; *************** *** 3878,3883 **** recheck_cast_function_args(List *args, Oid result_type, HeapTuple func_tuple) --- 3889,3895 ---- static Expr * evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, Oid result_collid, Oid input_collid, List *args, + bool funcvariadic, HeapTuple func_tuple, eval_const_expressions_context *context) { *************** *** 3959,3964 **** evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, --- 3971,3977 ---- newexpr->funcid = funcid; newexpr->funcresulttype = result_type; newexpr->funcretset = false; + newexpr->funcvariadic = funcvariadic; newexpr->funcformat = COERCE_EXPLICIT_CALL; /* doesn't matter */ newexpr->funccollid = result_collid; /* doesn't matter */ newexpr->inputcollid = input_collid; *** a/src/backend/parser/parse_func.c --- b/src/backend/parser/parse_func.c *************** *** 384,389 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, --- 384,390 ---- funcexpr->funcid = funcid; funcexpr->funcresulttype = rettype; funcexpr->funcretset = retset; + funcexpr->funcvariadic = func_variadic; funcexpr->funcformat = COERCE_EXPLICIT_CALL; /* funccollid and inputcollid will be set by parse_collate.c */ funcexpr->args = fargs; *** 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,85 ---- 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 void unpack_variadic(FunctionCallInfo fcinfo, int *nelems, Oid *vararg_typid, ! Datum **variadic_args, bool **variadic_nulls); static Datum text_to_array_internal(PG_FUNCTION_ARGS); static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v, *************** *** 3959,3964 **** text_format(PG_FUNCTION_ARGS) --- 3961,3973 ---- const char *end_ptr; text *result; int arg = 0; + bool funcvariadic; + int nargs; + Oid vararg_typid = InvalidOid; + Datum *varargs = NULL; + bool *varargs_nulls = NULL; + Oid typOutput = InvalidOid; + Oid prev_typid = InvalidOid; /* When format string is null, returns null */ if (PG_ARGISNULL(0)) *************** *** 3970,3975 **** text_format(PG_FUNCTION_ARGS) --- 3979,4002 ---- end_ptr = start_ptr + VARSIZE_ANY_EXHDR(fmt); initStringInfo(&str); + /* when last argument was marked as VARIADIC unpack variadic argument */ + if (get_fn_expr_arg_variadic(fcinfo->flinfo)) + { + int nelems; + + unpack_variadic(fcinfo, + &nelems, &vararg_typid, + &varargs, &varargs_nulls); + + nargs = PG_NARGS() - 1 + nelems; + funcvariadic = true; + } + else + { + nargs = PG_NARGS(); + funcvariadic = false; + } + /* Scan format string, looking for conversion specifiers. */ for (cp = start_ptr; cp < end_ptr; cp++) { *************** *** 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"))); --- 4089,4095 ---- } /* 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, --- 4099,4132 ---- * 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 = varargs[arg - 1]; ! isNull = varargs_nulls[arg - 1]; ! typid = vararg_typid; ! } ! ! /* try to reuse typOutput function */ ! if (typOutput == InvalidOid || prev_typid != typid) ! { ! bool typIsVarlena; ! ! getTypeOutputInfo(typid, &typOutput, &typIsVarlena); ! prev_typid = typid; ! } switch (*cp) { case 's': case 'I': case 'L': ! text_format_string_conversion(&str, *cp, typOutput, 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. */ --- 4140,4158 ---- result = cstring_to_text_with_len(str.data, str.len); pfree(str.data); + if (varargs != NULL) + pfree(varargs); + if (varargs_nulls != NULL) + pfree(varargs_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. */ --- 4168,4173 ---- *************** *** 4145,4150 **** text_format_string_conversion(StringInfo buf, char conversion, --- 4192,4266 ---- } /* + * unpack VARIADIC parameter - used by VARIADIC "any" functions, when + * parameter is labeled as VARIADIC + */ + static void + unpack_variadic(FunctionCallInfo fcinfo, int *nelems, Oid *vararg_typid, + Datum **variadic_args, bool **variadic_nulls) + { + Oid arr_typid; + ArrayType *arr; + Oid elem_type; + int slice; + ArrayIterator iterator; + int nitems; + Datum *args; + bool *nulls; + Datum value; + bool isnull; + int n = 0; + + /* sanity check, a variadic argument should be a non null array */ + if (fcinfo->argnull[fcinfo->nargs - 1]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("variadic argument cannot be null"))); + + 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"))); + + /* allow multidimensional arrays - use slicing when array is multidimensional */ + arr = DatumGetArrayTypeP(fcinfo->arg[fcinfo->nargs - 1]); + slice = ARR_NDIM(arr) > 1 ? ARR_NDIM(arr) - 1 : 0; + elem_type = slice > 0 ? arr_typid : ARR_ELEMTYPE(arr); + + iterator = array_create_iterator(arr, slice); + nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr)) + 10; + + /* preallocate memory for maximal number of items */ + args = (Datum *) palloc(nitems * sizeof(Datum)); + nulls = (bool *) palloc(nitems * sizeof(bool)); + + /* copy data from iterator */ + while (array_iterate(iterator, &value, &isnull)) + { + Assert(n < nitems); + + args[n] = value; + nulls[n] = isnull; + n++; + } + + /* Release iterator */ + array_free_iterator(iterator); + + /* push result */ + *nelems = n; + *vararg_typid = elem_type; + *variadic_args = args; + *variadic_nulls = nulls; + } + + /* * text_format_nv - nonvariadic wrapper for text_format function. * * note: this wrapper is necessary to pass the sanity check in opr_sanity, *** a/src/backend/utils/fmgr/fmgr.c --- b/src/backend/utils/fmgr/fmgr.c *************** *** 2307,2312 **** get_fn_expr_rettype(FmgrInfo *flinfo) --- 2307,2334 ---- } /* + * When last argument was labeled VARIADIC, then returns true, + * else returns false. + * + * Returns false if information is not available + */ + bool + get_fn_expr_arg_variadic(FmgrInfo *flinfo) + { + /* + * can't return anything useful if we have no FmgrInfo or if its fn_expr + * node has not been initialized - when we have not available fn_expr, then + * label VARIADIC should not be used. + */ + if (!flinfo || !flinfo->fn_expr) + return false; + + Assert(IsA(flinfo->fn_expr, FuncExpr)); + + return ((FuncExpr *) flinfo->fn_expr)->funcvariadic; + } + + /* * Get the actual type OID of a specific function argument (counting from 0) * * Returns InvalidOid if information is not available *** a/src/include/fmgr.h --- b/src/include/fmgr.h *************** *** 620,625 **** extern const Pg_finfo_record *fetch_finfo_record(void *filehandle, char *funcnam --- 620,626 ---- extern void clear_external_function_hash(void *filehandle); extern Oid fmgr_internal_function(const char *proname); extern Oid get_fn_expr_rettype(FmgrInfo *flinfo); + extern bool get_fn_expr_arg_variadic(FmgrInfo *flinfo); extern Oid get_fn_expr_argtype(FmgrInfo *flinfo, int argnum); extern Oid get_call_expr_argtype(fmNodePtr expr, int argnum); extern bool get_fn_expr_arg_stable(FmgrInfo *flinfo, int argnum); *** a/src/include/nodes/primnodes.h --- b/src/include/nodes/primnodes.h *************** *** 340,345 **** typedef struct FuncExpr --- 340,346 ---- Oid funcid; /* PG_PROC OID of the function */ Oid funcresulttype; /* PG_TYPE OID of result value */ bool funcretset; /* true if function returns set */ + bool funcvariadic; /* last argument was labeled VARIADIC */ CoercionForm funcformat; /* how to display this function call */ Oid funccollid; /* OID of collation of result */ Oid inputcollid; /* OID of collation that function should use */ *** a/src/test/regress/expected/text.out --- b/src/test/regress/expected/text.out *************** *** 241,243 **** select format('Hello %s %s, %2$s %2$s', 'World', 'Hello again'); --- 241,296 ---- 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) + + -- multidimensional array is supported + select format('%s, %s', variadic array[['Yellow','submarine'],['Hello','World']]); + format + ----------------------------------- + {Yellow,submarine}, {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) + + -- 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 *************** *** 76,78 **** select format('%1$1', 1); --- 76,95 ---- --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']); + -- multidimensional array is supported + select format('%s, %s', variadic array[['Yellow','submarine'],['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]); + + -- 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);