*** a/doc/src/sgml/func.sgml --- b/doc/src/sgml/func.sgml *************** *** 1538,1543 **** --- 1538,1561 ---- + + + format_array + + format_array(formatstr text + , params text[]) + + text + + Format a string. This function is similar to function format. + It uses same formating rules. But this function is not variadic - parameters are + passed in array - second parameter of this function. + + format_array('Hello %s, %1$s', array['World']) + Hello World, World + + + initcap *** a/src/backend/catalog/namespace.c --- b/src/backend/catalog/namespace.c *************** *** 1048,1053 **** FuncnameGetCandidates(List *names, int nargs, List *argnames, --- 1048,1061 ---- /* Ignore if it doesn't match requested argument count */ if (nargs >= 0 && pronargs != nargs && !variadic && !use_defaults) continue; + + /* + * Ignore variadic "ANY" function with disabled expand_variadic. + * There are no way, how to signalize target function about this + * situation. + */ + if (!expand_variadic && procform->provariadic == ANYOID) + continue; } /* *** a/src/backend/utils/adt/varlena.c --- b/src/backend/utils/adt/varlena.c *************** *** 50,55 **** typedef struct --- 50,72 ---- int skiptable[256]; /* skip distance for given mismatched char */ } TextPositionState; + typedef struct + { + FunctionCallInfo fcinfo; + int nargs; + } text_format_context; + + typedef struct + { + int nelems; + Datum *elem_values; + bool *elem_nulls; + Oid typOutput; + } text_format_array_context; + + typedef void (*param_feeder_type) (StringInfo str, const char conversion, + int arg, void *context); + #define DatumGetUnknownP(X) ((unknown *) PG_DETOAST_DATUM(X)) #define DatumGetUnknownPCopy(X) ((unknown *) PG_DETOAST_DATUM_COPY(X)) #define PG_GETARG_UNKNOWN_P(n) DatumGetUnknownP(PG_GETARG_DATUM(n)) *************** *** 76,87 **** 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, char *fldsep, char *null_string); /***************************************************************************** --- 93,107 ---- 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, ! Datum value, bool isNull, Oid typOutput); static Datum text_to_array_internal(PG_FUNCTION_ARGS); static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v, char *fldsep, char *null_string); + static void text_format_add_converted_arg(StringInfo str, + const char conversion, int arg, void *context); + static text *text_format_internal(text *fmt, param_feeder_type param_feeder, void *context); /***************************************************************************** *************** *** 3946,3958 **** text_reverse(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(result); } /* ! * Returns a formated string */ ! Datum ! text_format(PG_FUNCTION_ARGS) { - text *fmt; StringInfoData str; const char *cp; const char *start_ptr; --- 3966,3978 ---- PG_RETURN_TEXT_P(result); } + /* ! * generic function for format and format_array function */ ! static text * ! text_format_internal(text *fmt, param_feeder_type param_feeder, void *context) { StringInfoData str; const char *cp; const char *start_ptr; *************** *** 3960,3971 **** text_format(PG_FUNCTION_ARGS) text *result; int arg = 0; - /* When format string is null, returns null */ - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - /* Setup for main loop. */ - fmt = PG_GETARG_TEXT_PP(0); start_ptr = VARDATA_ANY(fmt); end_ptr = start_ptr + VARSIZE_ANY_EXHDR(fmt); initStringInfo(&str); --- 3980,3986 ---- *************** *** 3973,3982 **** text_format(PG_FUNCTION_ARGS) /* Scan format string, looking for conversion specifiers. */ for (cp = start_ptr; cp < end_ptr; cp++) { - Datum value; - bool isNull; - Oid typid; - /* * If it's not the start of a conversion specifier, just copy it to * the output buffer. --- 3988,3993 ---- *************** *** 4061,4087 **** text_format(PG_FUNCTION_ARGS) errmsg("conversion specifies argument 0, but arguments are numbered from 1"))); } - /* 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"))); - - /* - * 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); break; default: ereport(ERROR, --- 4072,4083 ---- errmsg("conversion specifies argument 0, but arguments are numbered from 1"))); } switch (*cp) { case 's': case 'I': case 'L': ! param_feeder(&str, *cp, arg, context); 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. */ --- 4091,4153 ---- result = cstring_to_text_with_len(str.data, str.len); pfree(str.data); ! return result; } ! /* ! * Assign formated parameter to output StringInfo ! */ ! static void ! text_format_add_converted_arg(StringInfo str, const char conversion, int arg, void *context) { + text_format_context *fctxt = (text_format_context *) context; + Datum value; + bool isNull; + Oid typid; Oid typOutput; bool typIsVarlena; + + /* Not enough arguments? Deduct 1 to avoid counting format string. */ + if (arg > fctxt->nargs - 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("too few arguments for format"))); + + value = fctxt->fcinfo->arg[arg]; + isNull = fctxt->fcinfo->argnull[arg]; + typid = get_fn_expr_argtype(fctxt->fcinfo->flinfo, arg); + + /* Stringify. */ + getTypeOutputInfo(typid, &typOutput, &typIsVarlena); + + text_format_string_conversion(str, conversion, value, isNull, typOutput); + } + + /* + * Returns a formated string + */ + Datum + text_format(PG_FUNCTION_ARGS) + { + text_format_context fctxt; + + /* When format string is null, returns null */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + fctxt.fcinfo = fcinfo; + fctxt.nargs = fcinfo->nargs; + + PG_RETURN_TEXT_P(text_format_internal(PG_GETARG_TEXT_PP(0), + text_format_add_converted_arg, + &fctxt)); + } + + /* Format a %s, %I, or %L conversion. */ + static void + text_format_string_conversion(StringInfo buf, char conversion, + Datum value, bool isNull, Oid typOutput) + { 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. */ --- 4163,4168 ---- *************** *** 4156,4158 **** text_format_nv(PG_FUNCTION_ARGS) --- 4198,4272 ---- { return text_format(fcinfo); } + + static void + text_format_array_add_converted_arg(StringInfo str, const char conversion, int arg, void *context) + { + text_format_array_context *fctxt = (text_format_array_context *) context; + Datum value; + bool isNull; + + /* Not enough arguments? Deduct 1 to avoid counting format string. */ + if (arg > fctxt->nelems) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("too few arguments for format"))); + + value = fctxt->elem_values[arg - 1]; + isNull = fctxt->elem_nulls[arg - 1]; + + text_format_string_conversion(str, conversion, value, isNull, fctxt->typOutput); + } + + /* + * Returns formated string - parameters are passed in array + */ + Datum + text_format_array(PG_FUNCTION_ARGS) + { + text *result; + text_format_array_context fctxt; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + if (!PG_ARGISNULL(1)) + { + ArrayType *v; + Oid elmtype; + int16 elmlen; + bool elmbyval; + char elmalign; + bool typbyval; + + v = PG_GETARG_ARRAYTYPE_P(1); + + elmtype = ARR_ELEMTYPE(v); + get_typlenbyvalalign(elmtype, &elmlen, &elmbyval, &elmalign); + + deconstruct_array(v, elmtype, + elmlen, elmbyval, elmalign, + &fctxt.elem_values, &fctxt.elem_nulls, + &fctxt.nelems); + + getTypeOutputInfo(elmtype, &fctxt.typOutput, &typbyval); + } + else + { + fctxt.nelems = 0; + fctxt.elem_values = NULL; + fctxt.elem_nulls = NULL; + fctxt.typOutput = InvalidOid; + } + + result = text_format_internal(PG_GETARG_TEXT_PP(0), + text_format_array_add_converted_arg, + &fctxt); + + if (fctxt.elem_values != NULL) + pfree(fctxt.elem_values); + if (fctxt.elem_nulls != NULL) + pfree(fctxt.elem_nulls); + + PG_RETURN_TEXT_P(result); + } *** a/src/include/catalog/pg_proc.h --- b/src/include/catalog/pg_proc.h *************** *** 2317,2322 **** DATA(insert OID = 3539 ( format PGNSP PGUID 12 1 0 2276 0 f f f f f f s 2 0 25 --- 2317,2324 ---- DESCR("format text message"); DATA(insert OID = 3540 ( format PGNSP PGUID 12 1 0 0 0 f f f f f f s 1 0 25 "25" _null_ _null_ _null_ _null_ text_format_nv _null_ _null_ _null_ )); DESCR("format text message"); + DATA(insert OID = 3839 ( format_array PGNSP PGUID 12 1 0 0 0 f f f f f f s 2 0 25 "25 2277" _null_ _null_ _null_ _null_ text_format_array _null_ _null_ _null_ )); + DESCR("format text message with array parameters"); DATA(insert OID = 1810 ( bit_length PGNSP PGUID 14 1 0 0 0 f f f f t f i 1 0 23 "17" _null_ _null_ _null_ _null_ "select pg_catalog.octet_length($1) * 8" _null_ _null_ _null_ )); DESCR("length in bits"); *** a/src/include/utils/builtins.h --- b/src/include/utils/builtins.h *************** *** 790,795 **** extern Datum text_right(PG_FUNCTION_ARGS); --- 790,796 ---- extern Datum text_reverse(PG_FUNCTION_ARGS); extern Datum text_format(PG_FUNCTION_ARGS); extern Datum text_format_nv(PG_FUNCTION_ARGS); + extern Datum text_format_array(PG_FUNCTION_ARGS); /* version.c */ extern Datum pgsql_version(PG_FUNCTION_ARGS); *** 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,262 ---- Hello World Hello again, Hello again Hello again (1 row) + -- should fail - cannot use VARIADIC when you call variadic "any" function + select format('Hello %s %1$s %s', variadic array['World', 'Hello again']); + ERROR: function format(unknown, text[]) does not exist + LINE 1: select format('Hello %s %1$s %s', variadic array['World', 'H... + ^ + HINT: No function matches the given name and argument types. You might need to add explicit type casts. + -- format array with format string + select format_array('Hello %s %1$s %s', array['World', 'Hello again']); + format_array + ------------------------------- + Hello World World Hello again + (1 row) + + select format_array('Hello %s %s, %2$s %2$s', array['World', 'Hello again']); + format_array + -------------------------------------------------- + Hello World Hello again, Hello again Hello again + (1 row) + *** a/src/test/regress/sql/text.sql --- b/src/test/regress/sql/text.sql *************** *** 76,78 **** select format('%1$1', 1); --- 76,85 ---- --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'); + + -- should fail - cannot use VARIADIC when you call variadic "any" function + select format('Hello %s %1$s %s', variadic array['World', 'Hello again']); + + -- format array with format string + select format_array('Hello %s %1$s %s', array['World', 'Hello again']); + select format_array('Hello %s %s, %2$s %2$s', array['World', 'Hello again']);