proposal: fix corner use case of variadic fuctions usage
Hello
here is patch, that enables using a variadic parameter modifier for
variadic "any" function.
Motivation for this patch is consistent behave of "format" function,
but it fixes behave of all this class variadic functions.
postgres=> -- check pass variadic argument
postgres=> select format('%s, %s', variadic array['Hello','World']);
format
──────────────
Hello, World
(1 row)
postgres=> -- multidimensional array is supported
postgres=> select format('%s, %s', variadic
array[['Nazdar','Svete'],['Hello','World']]);
format
───────────────────────────────
{Nazdar,Svete}, {Hello,World}
(1 row)
It respect Tom's comments - it is based on short-lived FmgrInfo
structures, that are created immediately before function invocation.
Note: there is unsolved issue - reusing transformed arguments - so it
is little bit suboptimal for VARIADIC RETURNING MultiFuncCall
functions, where we don't need to repeat a unpacking of array value.
Regards
Pavel
Attachments:
variadic_argument_for_variadic_any_function.diffapplication/octet-stream; name=variadic_argument_for_variadic_any_function.diffDownload
*** a/src/backend/catalog/pg_aggregate.c
--- b/src/backend/catalog/pg_aggregate.c
***************
*** 344,351 **** lookup_agg_function(List *fnName,
*/
fdresult = func_get_detail(fnName, NIL, NIL,
nargs, input_types, false, false,
! &fnOid, rettype, &retset, &nvargs,
! &true_oid_array, NULL);
/* only valid case is a normal function not returning a set */
if (fdresult != FUNCDETAIL_NORMAL || !OidIsValid(fnOid))
--- 344,351 ----
*/
fdresult = func_get_detail(fnName, NIL, NIL,
nargs, input_types, false, false,
! &fnOid, rettype, &retset, NULL,
! &nvargs, &true_oid_array, NULL);
/* only valid case is a normal function not returning a set */
if (fdresult != FUNCDETAIL_NORMAL || !OidIsValid(fnOid))
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
***************
*** 1728,1733 **** restart:
--- 1728,1839 ----
}
/*
+ * this is support for variadic function corner case - using variadic argument
+ * with VARIADIC modifier when variadic "any" function is called. In this case
+ * we have to create short lived flinfo and fcinfo, because function descriptors
+ * can be different in any call of variadic function - depends on content of
+ * variadic array argument.
+ */
+ if (IsA(fcache->xprstate.expr, FuncExpr) && ((FuncExpr *) fcache->xprstate.expr)->merge_vararg)
+ {
+ FmgrInfo sfinfo; /* short lived fake finfo */
+ FunctionCallInfoData scinfo; /* short lived fake fcinfo */
+ FuncExpr sfexpr; /* short lived fake fcinfo */
+ MemoryContext oldContext;
+ Oid vararg_type;
+ Oid elem_type;
+ bool elem_typbyval;
+ int16 elem_typlen;
+ char elem_typalign;
+ ArrayType *arr;
+ ArrayIterator iterator;
+ List *params_exprs;
+ ListCell *lc;
+ int n;
+ Datum value;
+ bool isnull;
+ int slice;
+ FuncExpr *fexpr = (FuncExpr *) fcache->xprstate.expr;
+
+ /* sanity check, a variadic argument should be a array */
+ if (fcinfo->argnull[fcinfo->nargs - 1])
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("variadic argument cannot be null")));
+
+ vararg_type = get_fn_expr_argtype(fcinfo->flinfo, fcinfo->nargs - 1);
+ if (!OidIsValid(vararg_type))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not determinate variadic argument data type")));
+ if (!OidIsValid(get_element_type(vararg_type)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("variadic argument should be an array")));
+
+ /* switch to short lived per-tuple context for node's copies */
+ oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+
+ arr = DatumGetArrayTypeP(fcinfo->arg[fcinfo->nargs - 1]);
+ slice = ARR_NDIM(arr) > 1 ? ARR_NDIM(arr) - 1 : 0;
+ elem_type = slice > 0 ? vararg_type : ARR_ELEMTYPE(arr);
+
+ /* ToDo: use element_type depends for dimensions */
+ get_typlenbyvalalign(elem_type,
+ &elem_typlen,
+ &elem_typbyval,
+ &elem_typalign);
+
+ /* create short lived copies of fmgr data */
+ fmgr_info_copy(&sfinfo, fcinfo->flinfo, fcinfo->flinfo->fn_mcxt);
+ memcpy(&scinfo, fcinfo, sizeof(FunctionCallInfoData));
+ scinfo.flinfo = &sfinfo;
+
+ iterator = array_create_iterator(arr, slice);
+
+ /*
+ * we have to create short lived fn_expr because variadic
+ * "any" functions takes types from expression nodes via get_fn_expr_argtype.
+ */
+ n = 1;
+ params_exprs = NIL;
+ foreach(lc, fexpr->args)
+ {
+ /* don't copy last argument */
+ if (n++ >= fcinfo->nargs)
+ break;
+ params_exprs = lappend(params_exprs, lc->data.ptr_value);
+ }
+
+ while (array_iterate(iterator, &value, &isnull))
+ {
+ /* replace variadic argument */
+ scinfo.arg[n - 2] = value;
+ scinfo.argnull[n - 2] = isnull;
+
+ params_exprs = lappend(params_exprs, makeConst(elem_type,
+ -1,
+ scinfo.fncollation,
+ elem_typlen,
+ value,
+ isnull,
+ elem_typbyval));
+ n++;
+ }
+
+ array_free_iterator(iterator);
+
+ memcpy(&sfexpr, fexpr, sizeof(FuncExpr));
+ sfexpr.args = params_exprs;
+ sfinfo.fn_expr = (fmNodePtr) &sfexpr;
+
+ scinfo.nargs = n;
+ fcinfo = &scinfo;
+
+ MemoryContextSwitchTo(oldContext);
+ }
+
+ /*
* Now call the function, passing the evaluated parameter values.
*/
if (fcache->func.fn_retset || hasSetArg)
*** 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(merge_vararg);
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(merge_vararg);
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->merge_vararg = 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(merge_vararg);
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(merge_vararg);
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
***************
*** 2330,2335 **** eval_const_expressions_mutator(Node *node,
--- 2330,2336 ----
newexpr->funcid = expr->funcid;
newexpr->funcresulttype = expr->funcresulttype;
newexpr->funcretset = expr->funcretset;
+ newexpr->merge_vararg = expr->merge_vararg;
newexpr->funcformat = expr->funcformat;
newexpr->funccollid = expr->funccollid;
newexpr->inputcollid = expr->inputcollid;
***************
*** 3625,3630 **** simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
--- 3626,3632 ----
fexpr.funcid = funcid;
fexpr.funcresulttype = result_type;
fexpr.funcretset = func_form->proretset;
+ fexpr.merge_vararg = false;
fexpr.funcformat = COERCE_EXPLICIT_CALL;
fexpr.funccollid = result_collid;
fexpr.inputcollid = input_collid;
***************
*** 3959,3964 **** evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
--- 3961,3967 ----
newexpr->funcid = funcid;
newexpr->funcresulttype = result_type;
newexpr->funcretset = false;
+ newexpr->merge_vararg = false;
newexpr->funcformat = COERCE_EXPLICIT_CALL; /* doesn't matter */
newexpr->funccollid = result_collid; /* doesn't matter */
newexpr->inputcollid = input_collid;
***************
*** 4089,4094 **** inline_function(Oid funcid, Oid result_type, Oid result_collid,
--- 4092,4098 ----
fexpr->funcid = funcid;
fexpr->funcresulttype = result_type;
fexpr->funcretset = false;
+ fexpr->merge_vararg = false; /* SQL cannot be variadic "any" */
fexpr->funcformat = COERCE_EXPLICIT_CALL; /* doesn't matter */
fexpr->funccollid = result_collid; /* doesn't matter */
fexpr->inputcollid = input_collid;
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
***************
*** 78,83 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 78,84 ----
List *argdefaults;
Node *retval;
bool retset;
+ bool merge_vararg;
int nvargs;
FuncDetailCode fdresult;
***************
*** 214,221 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
fdresult = func_get_detail(funcname, fargs, argnames, nargs,
actual_arg_types,
!func_variadic, true,
! &funcid, &rettype, &retset, &nvargs,
! &declared_arg_types, &argdefaults);
if (fdresult == FUNCDETAIL_COERCION)
{
/*
--- 215,222 ----
fdresult = func_get_detail(funcname, fargs, argnames, nargs,
actual_arg_types,
!func_variadic, true,
! &funcid, &rettype, &retset, &merge_vararg,
! &nvargs, &declared_arg_types, &argdefaults);
if (fdresult == FUNCDETAIL_COERCION)
{
/*
***************
*** 384,389 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 385,391 ----
funcexpr->funcid = funcid;
funcexpr->funcresulttype = rettype;
funcexpr->funcretset = retset;
+ funcexpr->merge_vararg = merge_vararg;
funcexpr->funcformat = COERCE_EXPLICIT_CALL;
/* funccollid and inputcollid will be set by parse_collate.c */
funcexpr->args = fargs;
***************
*** 1001,1006 **** func_select_candidate(int nargs,
--- 1003,1017 ----
* they don't need that check made. Note also that when fargnames isn't NIL,
* the fargs list must be passed if the caller wants actual argument position
* information to be returned into the NamedArgExpr nodes.
+ *
+ * Special use case is a call variadic function with "any" variadic parameter and
+ * with variadic argument - argument market VARIADIC modifier. Variadic "any"
+ * function expect unpacked variadic arguments passed in fcinfo->args like usual
+ * parameter. Others variadic functions expect variadic argument as a array. So
+ * when we don't wont expand variadic parameter and we have variadic "any"
+ * function, we have to unpack a array to fcinfo->args array and we have to
+ * create short life FmgrInfo fake structure. When this use case is identified,
+ * then output in merge_vararg is true.
*/
FuncDetailCode
func_get_detail(List *funcname,
***************
*** 1013,1018 **** func_get_detail(List *funcname,
--- 1024,1030 ----
Oid *funcid, /* return value */
Oid *rettype, /* return value */
bool *retset, /* return value */
+ bool *merge_vararg, /* optional return value */
int *nvargs, /* return value */
Oid **true_typeids, /* return value */
List **argdefaults) /* optional return value */
***************
*** 1301,1306 **** func_get_detail(List *funcname,
--- 1313,1322 ----
*argdefaults = defaults;
}
}
+
+ if (merge_vararg != NULL)
+ *merge_vararg = !expand_variadic && pform->provariadic == ANYOID;
+
if (pform->proisagg)
result = FUNCDETAIL_AGGREGATE;
else if (pform->proiswindow)
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 7437,7443 **** generate_function_name(Oid funcid, int nargs, List *argnames,
NIL, argnames, nargs, argtypes,
!OidIsValid(procform->provariadic), true,
&p_funcid, &p_rettype,
! &p_retset, &p_nvargs, &p_true_typeids, NULL);
if ((p_result == FUNCDETAIL_NORMAL ||
p_result == FUNCDETAIL_AGGREGATE ||
p_result == FUNCDETAIL_WINDOWFUNC) &&
--- 7437,7443 ----
NIL, argnames, nargs, argtypes,
!OidIsValid(procform->provariadic), true,
&p_funcid, &p_rettype,
! &p_retset, NULL, &p_nvargs, &p_true_typeids, NULL);
if ((p_result == FUNCDETAIL_NORMAL ||
p_result == FUNCDETAIL_AGGREGATE ||
p_result == FUNCDETAIL_WINDOWFUNC) &&
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 340,345 **** typedef struct FuncExpr
--- 340,347 ----
Oid funcid; /* PG_PROC OID of the function */
Oid funcresulttype; /* PG_TYPE OID of result value */
bool funcretset; /* true if function returns set */
+ bool merge_vararg; /* if true, then do unpack last array argument */
+ /* to fmgr args */
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/include/parser/parse_func.h
--- b/src/include/parser/parse_func.h
***************
*** 53,59 **** extern FuncDetailCode func_get_detail(List *funcname,
int nargs, Oid *argtypes,
bool expand_variadic, bool expand_defaults,
Oid *funcid, Oid *rettype,
! bool *retset, int *nvargs, Oid **true_typeids,
List **argdefaults);
extern int func_match_argtypes(int nargs,
--- 53,60 ----
int nargs, Oid *argtypes,
bool expand_variadic, bool expand_defaults,
Oid *funcid, Oid *rettype,
! bool *retset, bool *merge_vararg,
! int *nvargs, Oid **true_typeids,
List **argdefaults);
extern int func_match_argtypes(int nargs,
*** a/src/test/regress/expected/text.out
--- b/src/test/regress/expected/text.out
***************
*** 228,234 **** select format('%1$', 1);
ERROR: unterminated conversion specifier
select format('%1$1', 1);
ERROR: unrecognized conversion specifier "1"
! --checkk mix of positional and ordered placeholders
select format('Hello %s %1$s %s', 'World', 'Hello again');
format
-------------------------------
--- 228,234 ----
ERROR: unterminated conversion specifier
select format('%1$1', 1);
ERROR: unrecognized conversion specifier "1"
! --check mix of positional and ordered placeholders
select format('Hello %s %1$s %s', 'World', 'Hello again');
format
-------------------------------
***************
*** 241,243 **** select format('Hello %s %s, %2$s %2$s', 'World', 'Hello again');
--- 241,289 ----
Hello World Hello again, Hello again Hello again
(1 row)
+ -- check pass variadic argument
+ select format('%s, %s', variadic array['Hello','World']);
+ format
+ --------------
+ Hello, World
+ (1 row)
+
+ -- multidimensional array is supported
+ select format('%s, %s', variadic array[['Nazdar','Svete'],['Hello','World']]);
+ format
+ -------------------------------
+ {Nazdar,Svete}, {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)
+
*** a/src/test/regress/sql/text.sql
--- b/src/test/regress/sql/text.sql
***************
*** 73,78 **** select format('%1$s %13$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
select format('%1s', 1);
select format('%1$', 1);
select format('%1$1', 1);
! --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');
--- 73,91 ----
select format('%1s', 1);
select format('%1$', 1);
select format('%1$1', 1);
! --check 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 argument
+ select format('%s, %s', variadic array['Hello','World']);
+ -- multidimensional array is supported
+ select format('%s, %s', variadic array[['Nazdar','Svete'],['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]);
On Sun, Nov 4, 2012 at 12:49 PM, Pavel Stehule <pavel.stehule@gmail.com>wrote:
Hello
here is patch, that enables using a variadic parameter modifier for
variadic "any" function.Motivation for this patch is consistent behave of "format" function,
but it fixes behave of all this class variadic functions.postgres=> -- check pass variadic argument
postgres=> select format('%s, %s', variadic array['Hello','World']);
format
──────────────
Hello, World
(1 row)postgres=> -- multidimensional array is supported
postgres=> select format('%s, %s', variadic
array[['Nazdar','Svete'],['Hello','World']]);
format
───────────────────────────────
{Nazdar,Svete}, {Hello,World}
(1 row)It respect Tom's comments - it is based on short-lived FmgrInfo
structures, that are created immediately before function invocation.Note: there is unsolved issue - reusing transformed arguments - so it
is little bit suboptimal for VARIADIC RETURNING MultiFuncCall
functions, where we don't need to repeat a unpacking of array value.Regards
Pavel
Hi Pavel.
I am trying to review this patch and on my work computer everything
compiles and tests perfectly. However, on my laptop, the regression tests
don't pass with "cache lookup failed for type XYZ" where XYZ is some number
that does not appear to be any type oid.
I don't really know where to go from here. I am asking that other people
try this patch to see if they get errors as well.
Vik
PS: I won't be able to answer this thread until Tuesday.
Hello
Hi Pavel.
I am trying to review this patch and on my work computer everything compiles
and tests perfectly. However, on my laptop, the regression tests don't pass
with "cache lookup failed for type XYZ" where XYZ is some number that does
not appear to be any type oid.I don't really know where to go from here. I am asking that other people try
this patch to see if they get errors as well.
yes, I checked it on .x86_64 and I had a same problems
probably there was more than one issue - I had to fix a creating a
unpacked params and I had a issue with gcc optimalization when I used
a stack variable for fcinfo.
Now I fixed these issues and I hope so it will work on all platforms
Regards
Pavel Stehule
Show quoted text
Vik
PS: I won't be able to answer this thread until Tuesday.
Attachments:
variadic_argument_for_variadic_any_function_20121201.diffapplication/octet-stream; name=variadic_argument_for_variadic_any_function_20121201.diffDownload
*** a/src/backend/catalog/pg_aggregate.c
--- b/src/backend/catalog/pg_aggregate.c
***************
*** 344,351 **** lookup_agg_function(List *fnName,
*/
fdresult = func_get_detail(fnName, NIL, NIL,
nargs, input_types, false, false,
! &fnOid, rettype, &retset, &nvargs,
! &true_oid_array, NULL);
/* only valid case is a normal function not returning a set */
if (fdresult != FUNCDETAIL_NORMAL || !OidIsValid(fnOid))
--- 344,351 ----
*/
fdresult = func_get_detail(fnName, NIL, NIL,
nargs, input_types, false, false,
! &fnOid, rettype, &retset, NULL,
! &nvargs, &true_oid_array, NULL);
/* only valid case is a normal function not returning a set */
if (fdresult != FUNCDETAIL_NORMAL || !OidIsValid(fnOid))
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
***************
*** 1728,1733 **** restart:
--- 1728,1843 ----
}
/*
+ * this is support for variadic function corner case - using variadic argument
+ * with VARIADIC modifier when variadic "any" function is called. In this case
+ * we have to create short lived flinfo and fcinfo, because function descriptors
+ * can be different in any call of variadic function - depends on content of
+ * variadic array argument.
+ */
+ if (IsA(fcache->xprstate.expr, FuncExpr) && ((FuncExpr *) fcache->xprstate.expr)->merge_vararg)
+ {
+ FmgrInfo sfinfo; /* short lived fake finfo */
+ FunctionCallInfo scinfo; /* short lived fake fcinfo */
+ FuncExpr sfexpr; /* short lived fake fcinfo */
+ MemoryContext oldContext;
+ Oid vararg_type;
+ Oid elem_type;
+ bool elem_typbyval;
+ int16 elem_typlen;
+ char elem_typalign;
+ ArrayType *arr;
+ ArrayIterator iterator;
+ List *params_exprs;
+ ListCell *lc;
+ int n;
+ Datum value;
+ bool isnull;
+ int slice;
+ FuncExpr *fexpr = (FuncExpr *) fcache->xprstate.expr;
+
+ /* sanity check, a variadic argument should be a array */
+ if (fcinfo->argnull[fcinfo->nargs - 1])
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("variadic argument cannot be null")));
+
+ vararg_type = get_fn_expr_argtype(fcinfo->flinfo, fcinfo->nargs - 1);
+ if (!OidIsValid(vararg_type))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not determinate variadic argument data type")));
+ if (!OidIsValid(get_element_type(vararg_type)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("variadic argument should be an array")));
+
+ /* switch to short lived per-tuple context for node's copies */
+ oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+
+ arr = DatumGetArrayTypeP(fcinfo->arg[fcinfo->nargs - 1]);
+ slice = ARR_NDIM(arr) > 1 ? ARR_NDIM(arr) - 1 : 0;
+ elem_type = slice > 0 ? vararg_type : ARR_ELEMTYPE(arr);
+
+ /* ToDo: use element_type depends for dimensions */
+ get_typlenbyvalalign(elem_type,
+ &elem_typlen,
+ &elem_typbyval,
+ &elem_typalign);
+
+ scinfo = (FunctionCallInfo) palloc(sizeof(FunctionCallInfoData));
+
+ /* create short lived copies of fmgr data */
+ fmgr_info_copy(&sfinfo, fcinfo->flinfo, fcinfo->flinfo->fn_mcxt);
+ memcpy(scinfo, fcinfo, sizeof(FunctionCallInfoData));
+ scinfo->flinfo = &sfinfo;
+
+ iterator = array_create_iterator(arr, slice);
+
+ /*
+ * we have to create short lived fn_expr because variadic
+ * "any" functions takes types from expression nodes via get_fn_expr_argtype.
+ */
+ n = 0;
+ params_exprs = NIL;
+ foreach(lc, fexpr->args)
+ {
+ /* don't copy last "variadic argument */
+ if (n >= fcinfo->nargs - 1)
+ break;
+
+ params_exprs = lappend(params_exprs, lc->data.ptr_value);
+ n++;
+ }
+
+ while (array_iterate(iterator, &value, &isnull))
+ {
+ /* replace variadic argument */
+ scinfo->arg[n] = value;
+ scinfo->argnull[n] = isnull;
+
+ params_exprs = lappend(params_exprs, makeConst(elem_type,
+ -1,
+ scinfo->fncollation,
+ elem_typlen,
+ value,
+ isnull,
+ elem_typbyval));
+ n++;
+ }
+
+ array_free_iterator(iterator);
+
+ memcpy(&sfexpr, fexpr, sizeof(FuncExpr));
+ sfexpr.args = params_exprs;
+ sfinfo.fn_expr = (fmNodePtr) &sfexpr;
+
+ scinfo->nargs = n;
+ fcinfo = scinfo;
+
+ MemoryContextSwitchTo(oldContext);
+ }
+
+ /*
* Now call the function, passing the evaluated parameter values.
*/
if (fcache->func.fn_retset || hasSetArg)
*** 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(merge_vararg);
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(merge_vararg);
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->merge_vararg = 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(merge_vararg);
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(merge_vararg);
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
***************
*** 2330,2335 **** eval_const_expressions_mutator(Node *node,
--- 2330,2336 ----
newexpr->funcid = expr->funcid;
newexpr->funcresulttype = expr->funcresulttype;
newexpr->funcretset = expr->funcretset;
+ newexpr->merge_vararg = expr->merge_vararg;
newexpr->funcformat = expr->funcformat;
newexpr->funccollid = expr->funccollid;
newexpr->inputcollid = expr->inputcollid;
***************
*** 3625,3630 **** simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
--- 3626,3632 ----
fexpr.funcid = funcid;
fexpr.funcresulttype = result_type;
fexpr.funcretset = func_form->proretset;
+ fexpr.merge_vararg = false;
fexpr.funcformat = COERCE_EXPLICIT_CALL;
fexpr.funccollid = result_collid;
fexpr.inputcollid = input_collid;
***************
*** 3959,3964 **** evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
--- 3961,3967 ----
newexpr->funcid = funcid;
newexpr->funcresulttype = result_type;
newexpr->funcretset = false;
+ newexpr->merge_vararg = false;
newexpr->funcformat = COERCE_EXPLICIT_CALL; /* doesn't matter */
newexpr->funccollid = result_collid; /* doesn't matter */
newexpr->inputcollid = input_collid;
***************
*** 4089,4094 **** inline_function(Oid funcid, Oid result_type, Oid result_collid,
--- 4092,4098 ----
fexpr->funcid = funcid;
fexpr->funcresulttype = result_type;
fexpr->funcretset = false;
+ fexpr->merge_vararg = false; /* SQL cannot be variadic "any" */
fexpr->funcformat = COERCE_EXPLICIT_CALL; /* doesn't matter */
fexpr->funccollid = result_collid; /* doesn't matter */
fexpr->inputcollid = input_collid;
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
***************
*** 78,83 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 78,84 ----
List *argdefaults;
Node *retval;
bool retset;
+ bool merge_vararg;
int nvargs;
FuncDetailCode fdresult;
***************
*** 214,221 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
fdresult = func_get_detail(funcname, fargs, argnames, nargs,
actual_arg_types,
!func_variadic, true,
! &funcid, &rettype, &retset, &nvargs,
! &declared_arg_types, &argdefaults);
if (fdresult == FUNCDETAIL_COERCION)
{
/*
--- 215,222 ----
fdresult = func_get_detail(funcname, fargs, argnames, nargs,
actual_arg_types,
!func_variadic, true,
! &funcid, &rettype, &retset, &merge_vararg,
! &nvargs, &declared_arg_types, &argdefaults);
if (fdresult == FUNCDETAIL_COERCION)
{
/*
***************
*** 384,389 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 385,391 ----
funcexpr->funcid = funcid;
funcexpr->funcresulttype = rettype;
funcexpr->funcretset = retset;
+ funcexpr->merge_vararg = merge_vararg;
funcexpr->funcformat = COERCE_EXPLICIT_CALL;
/* funccollid and inputcollid will be set by parse_collate.c */
funcexpr->args = fargs;
***************
*** 1001,1006 **** func_select_candidate(int nargs,
--- 1003,1017 ----
* they don't need that check made. Note also that when fargnames isn't NIL,
* the fargs list must be passed if the caller wants actual argument position
* information to be returned into the NamedArgExpr nodes.
+ *
+ * Special use case is a call variadic function with "any" variadic parameter and
+ * with variadic argument - argument market VARIADIC modifier. Variadic "any"
+ * function expect unpacked variadic arguments passed in fcinfo->args like usual
+ * parameter. Others variadic functions expect variadic argument as a array. So
+ * when we don't wont expand variadic parameter and we have variadic "any"
+ * function, we have to unpack a array to fcinfo->args array and we have to
+ * create short life FmgrInfo fake structure. When this use case is identified,
+ * then output in merge_vararg is true.
*/
FuncDetailCode
func_get_detail(List *funcname,
***************
*** 1013,1018 **** func_get_detail(List *funcname,
--- 1024,1030 ----
Oid *funcid, /* return value */
Oid *rettype, /* return value */
bool *retset, /* return value */
+ bool *merge_vararg, /* optional return value */
int *nvargs, /* return value */
Oid **true_typeids, /* return value */
List **argdefaults) /* optional return value */
***************
*** 1301,1306 **** func_get_detail(List *funcname,
--- 1313,1322 ----
*argdefaults = defaults;
}
}
+
+ if (merge_vararg != NULL)
+ *merge_vararg = !expand_variadic && pform->provariadic == ANYOID;
+
if (pform->proisagg)
result = FUNCDETAIL_AGGREGATE;
else if (pform->proiswindow)
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 7437,7443 **** generate_function_name(Oid funcid, int nargs, List *argnames,
NIL, argnames, nargs, argtypes,
!OidIsValid(procform->provariadic), true,
&p_funcid, &p_rettype,
! &p_retset, &p_nvargs, &p_true_typeids, NULL);
if ((p_result == FUNCDETAIL_NORMAL ||
p_result == FUNCDETAIL_AGGREGATE ||
p_result == FUNCDETAIL_WINDOWFUNC) &&
--- 7437,7443 ----
NIL, argnames, nargs, argtypes,
!OidIsValid(procform->provariadic), true,
&p_funcid, &p_rettype,
! &p_retset, NULL, &p_nvargs, &p_true_typeids, NULL);
if ((p_result == FUNCDETAIL_NORMAL ||
p_result == FUNCDETAIL_AGGREGATE ||
p_result == FUNCDETAIL_WINDOWFUNC) &&
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 340,345 **** typedef struct FuncExpr
--- 340,347 ----
Oid funcid; /* PG_PROC OID of the function */
Oid funcresulttype; /* PG_TYPE OID of result value */
bool funcretset; /* true if function returns set */
+ bool merge_vararg; /* if true, then do unpack last array argument */
+ /* to fmgr args */
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/include/parser/parse_func.h
--- b/src/include/parser/parse_func.h
***************
*** 53,59 **** extern FuncDetailCode func_get_detail(List *funcname,
int nargs, Oid *argtypes,
bool expand_variadic, bool expand_defaults,
Oid *funcid, Oid *rettype,
! bool *retset, int *nvargs, Oid **true_typeids,
List **argdefaults);
extern int func_match_argtypes(int nargs,
--- 53,60 ----
int nargs, Oid *argtypes,
bool expand_variadic, bool expand_defaults,
Oid *funcid, Oid *rettype,
! bool *retset, bool *merge_vararg,
! int *nvargs, Oid **true_typeids,
List **argdefaults);
extern int func_match_argtypes(int nargs,
*** a/src/test/regress/expected/text.out
--- b/src/test/regress/expected/text.out
***************
*** 228,234 **** select format('%1$', 1);
ERROR: unterminated conversion specifier
select format('%1$1', 1);
ERROR: unrecognized conversion specifier "1"
! --checkk mix of positional and ordered placeholders
select format('Hello %s %1$s %s', 'World', 'Hello again');
format
-------------------------------
--- 228,234 ----
ERROR: unterminated conversion specifier
select format('%1$1', 1);
ERROR: unrecognized conversion specifier "1"
! --check mix of positional and ordered placeholders
select format('Hello %s %1$s %s', 'World', 'Hello again');
format
-------------------------------
***************
*** 241,243 **** select format('Hello %s %s, %2$s %2$s', 'World', 'Hello again');
--- 241,289 ----
Hello World Hello again, Hello again Hello again
(1 row)
+ -- check pass variadic argument
+ select format('%s, %s', variadic array['Hello','World']);
+ format
+ --------------
+ Hello, World
+ (1 row)
+
+ -- multidimensional array is supported
+ select format('%s, %s', variadic array[['Nazdar','Svete'],['Hello','World']]);
+ format
+ -------------------------------
+ {Nazdar,Svete}, {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)
+
*** a/src/test/regress/sql/text.sql
--- b/src/test/regress/sql/text.sql
***************
*** 73,78 **** select format('%1$s %13$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
select format('%1s', 1);
select format('%1$', 1);
select format('%1$1', 1);
! --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');
--- 73,91 ----
select format('%1s', 1);
select format('%1$', 1);
select format('%1$1', 1);
! --check 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 argument
+ select format('%s, %s', variadic array['Hello','World']);
+ -- multidimensional array is supported
+ select format('%s, %s', variadic array[['Nazdar','Svete'],['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]);
On 30.11.2012 12:18, Vik Reykja wrote:
I am trying to review this patch and on my work computer everything
compiles and tests perfectly. However, on my laptop, the regression tests
don't pass with "cache lookup failed for type XYZ" where XYZ is some number
that does not appear to be any type oid.I don't really know where to go from here. I am asking that other people
try this patch to see if they get errors as well.
I get the same error. I'll mark this as "waiting on author" in the
commitfest.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2012/12/5 Heikki Linnakangas <hlinnakangas@vmware.com>:
On 30.11.2012 12:18, Vik Reykja wrote:
I am trying to review this patch and on my work computer everything
compiles and tests perfectly. However, on my laptop, the regression tests
don't pass with "cache lookup failed for type XYZ" where XYZ is some
number
that does not appear to be any type oid.I don't really know where to go from here. I am asking that other people
try this patch to see if they get errors as well.
I get the same error. I'll mark this as "waiting on author" in the
commitfest.
it was with new patch?
Regards
Pavel
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 05.12.2012 10:38, Pavel Stehule wrote:
2012/12/5 Heikki Linnakangas<hlinnakangas@vmware.com>:
I get the same error. I'll mark this as "waiting on author" in the
commitfest.it was with new patch?
Ah, no, sorry. The new patch passes regressions just fine. Never mind..
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2012/12/5 Heikki Linnakangas <hlinnakangas@vmware.com>:
On 05.12.2012 10:38, Pavel Stehule wrote:
2012/12/5 Heikki Linnakangas<hlinnakangas@vmware.com>:
I get the same error. I'll mark this as "waiting on author" in the
commitfest.it was with new patch?
Ah, no, sorry. The new patch passes regressions just fine. Never mind..
:)
Pavel
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Dec 1, 2012 at 1:14 PM, Pavel Stehule <pavel.stehule@gmail.com>wrote:
Hello
Hi Pavel.
I am trying to review this patch and on my work computer everything
compiles
and tests perfectly. However, on my laptop, the regression tests don't
pass
with "cache lookup failed for type XYZ" where XYZ is some number that
does
not appear to be any type oid.
I don't really know where to go from here. I am asking that other people
try
this patch to see if they get errors as well.
yes, I checked it on .x86_64 and I had a same problems
probably there was more than one issue - I had to fix a creating a
unpacked params and I had a issue with gcc optimalization when I used
a stack variable for fcinfo.Now I fixed these issues and I hope so it will work on all platforms
It appears to work a lot better, yes. I played around with it a little bit
and wasn't able to break it, so I'm marking it as ready for committer.
Some wordsmithing will need to be done on the code comments.
Pavel,
* Pavel Stehule (pavel.stehule@gmail.com) wrote:
Now I fixed these issues and I hope so it will work on all platforms
As mentioned on the commitfest application, this needs documentation.
That is not the responsibility of the committer; if you need help, then
please ask for it.
I've also done a quick review of it.
The massive if() block being added to execQual.c:ExecMakeFunctionResult
really looks like it might be better pulled out into a function of its
own.
What does "use element_type depends for dimensions" mean and why is it
a ToDo? When will it be done?
Overall, the comments really need to be better. Things like this:
+ /* create short lived copies of fmgr data */
+ fmgr_info_copy(&sfinfo, fcinfo->flinfo, fcinfo->flinfo->fn_mcxt);
+ memcpy(scinfo, fcinfo, sizeof(FunctionCallInfoData));
+ scinfo->flinfo = &sfinfo;
Really don't cut it, imv. *Why* are we creating a copy of the fmgr
data? Why does it need to be short lived? And what is going to happen
later when you do this?:
fcinfo = scinfo;
MemoryContextSwitchTo(oldContext);
Is there any reason to worry about the fact that fcache->fcinfo_data no
longer matches the fcinfo that we're working on? What if there are
other references made to it in the future? These memcpy's and pointer
foolishness really don't strike me as being a well thought out approach.
There are other similar comments throughout which really need to be
rewritten to address why we're doing something, not what is being done-
you can read the code for that.
Marking this Waiting on Author.
Thanks,
Stephen
Stephen Frost <sfrost@snowman.net> writes:
[ quick review of patch ]
On reflection it seems to me that this is probably not a very good
approach overall. Our general theory for functions taking ANY has
been that the core system just computes the arguments and leaves it
to the function to make sense of them. Why should this be different
in the one specific case of VARIADIC ANY with a variadic array?
The approach is also inherently seriously inefficient. Not only
does ExecMakeFunctionResult have to build a separate phony Const
node for each array element, but the variadic function itself can
have no idea that those Consts are all the same type, which means
it's going to do datatype lookup over again for each array element.
(format(), for instance, would have to look up the same type output
function over and over again.) This might not be noticeable on toy
examples with a couple of array entries, but it'll be a large
performance problem on large arrays.
The patch also breaks any attempt that a variadic function might be
making to cache info about its argument types across calls. I suppose
that the copying of the FmgrInfo is a hack to avoid crashes due to
called functions supposing that their flinfo->fn_extra data is still
valid for the new argument set. Of course that's another pretty
significant performance hit, compounding the per-element hit. Whereas
an ordinary variadic function that is given N arguments in a particular
query will probably do N datatype lookups and cache the info for the
life of the query, a variadic function called with this approach will
do one datatype lookup for each array element in each row of the query;
and there is no way to optimize that.
But large arrays have a worse problem: the approach flat out does
not work for arrays of more than FUNC_MAX_ARGS elements, because
there's no place to put their values in the FunctionCallInfo struct.
(As submitted, if the array is too big the patch will blithely stomp
all over memory with user-supplied data, making it not merely a crash
risk but probably an exploitable security hole.)
This last problem is, so far as I can see, unfixable within this
approach; surely we are not going to accept a feature that only works
for small arrays. So I am going to mark the CF item rejected not just
RWF.
I believe that a workable approach would require having the function
itself act differently when the VARIADIC flag is set. Currently there
is no way for ANY-taking functions to do this because we don't record
the use of the VARIADIC flag in FuncExpr, but it'd be reasonable to
change that, and probably then add a function similar to
get_fn_expr_rettype to dig it out of the FmgrInfo. I don't know if
we could usefully provide any infrastructure beyond that for the case,
but it'd be worth thinking about whether there's any common behavior
for such functions.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello
I try to recapitulate our design of variadic functions (sorry for my
English - really I have plan to learn this language better - missing
time).
We have two kinds of VARG functions: VARIADIC "any" and VARIADIC
anyarray. These kinds are really different although it is transparent
for final user.
Our low level design supports variadic functions - it based on
FunctionCallInfoData structure. But SQL functions are designed as
nonvariadic overloaded functions - described by pg_proc tuple and
later FmgrInfo structure. This structure contains fn_nargs and fn_expr
(pointer to related parser node) and others, but fn_nargs and fn_epxr
are important for this moment. When we would to get parameter of any
function parameter, we have to use a get_fn_expr_argtype, that is
based on some magic over parser nodes. This expect static number of
parameters of any function during query execution - FmgrInfo is
significantly reused.
So variadic SQL function break some old expectation - but only
partially - with using some trick we are able use variadic function
inside SQL space without significant changes.
1) CALL of variadic functions are transformed to CALL of function with
fixed number of parameters - variadic parameters are transformed to
array
2) if you call variadic function in non variadic manner, then its
calling function with fixed number of parameters and then there
transformation are not necessary.
Points @1 and @2 are valid for VARIADIC anyarray functions.
We introduced VARIADIC "any" function. Motivation for this kind of
function was bypassing postgresql's coerce rules - and own rules
implementation for requested functionality. Some builtins function
does it internally - but it is not possible for custom functions or it
is not possible without some change in parser. Same motivation is
reason why "format" function is VARIADIC "any" function.
Internal implementation of this function is very simple - just enhance
internal checks for mutable number of parameters - and doesn't do
anything more - parameters are passed with FunctionCallInfoData,
necessary expression nodes are created natively. There is important
fact - number of parameters are immutable per one usage - so we can
reuse FmgrInfo. Current limit of VARIADIC "any" function is support
for call in non variadic manner - originally I didn't propose call in
non variadic manner and later we missed support for this use case.
What is worse, we don't handle this use case corectly now - call in
non variadic manner is quietly and buggy forwarded to variadic call
(keyword VARIADIC is ignored)
so we can
SELECT myleast(10,20,40);
SELECT myleast(VARIADIC array[10,20,40]);
SELECT format("%d %d", 10, 20);
but SELECT format("%d %d", VARIADIC array[10, 20]) returns wrong
result because it is not implemented
I proposed more solutions
a) disabling nob variadic call for VARIADIC "any" function and using
overloading as solution for this use case. It was rejected - it has
some issues due possible signature ambiguity.
b) enhancing FunctionCallInfoData about manner of call - variadic, or
non variadic. It was rejected - it needs fixing of all current
VARIADIC "any" functions and probably duplicate some work that can be
handled generic routine.
I don't defend these proposals too much - a issues are clear.
c) enhancing executor, so it can transform non variadic call to
variadic call - just quietly unpack array with parameters and stores
values to FunctionCallInfoData structure. There is new issue - we
cannot reuse parser's nodes and fn_expr and FmgrInfo structure -
because with this transformation these data are mutable.
some example
CREATE TABLE test(format_str text, params text[]);
INSERT INTO test VALUES('%s', ARRAY['Hello']);
INSERT INTO test VALUES('%s %s', ARRAY['Hello','World']);
SELECT format(format_str, VARIADIC params) FROM test;
now I have to support two instances of function - format('%s',
'Hello') and format('%s %s', 'Hello','World') with two different
FmgrInfo - mainly with two different fake parser nodes.
any call needs different FmgrInfo - and I am creating it in short
context due safe design - any forgotten pointer to this mutable
FmgrInfo or more precisely flinfo->fn_expr returns cleaned memory
(with enabled asserts) .
2013/1/18 Stephen Frost <sfrost@snowman.net>:
Pavel,
* Pavel Stehule (pavel.stehule@gmail.com) wrote:
Now I fixed these issues and I hope so it will work on all platforms
As mentioned on the commitfest application, this needs documentation.
That is not the responsibility of the committer; if you need help, then
please ask for it.I've also done a quick review of it.
The massive if() block being added to execQual.c:ExecMakeFunctionResult
really looks like it might be better pulled out into a function of its
own.
good idea
What does "use element_type depends for dimensions" mean and why is it
a ToDo? When will it be done?
I had to thinking some time :( - forgotten note - should be removed
Actually it should to support multidimensional array as parameter's
array - and it does one dimensional slicing - so passing arrays to
variadic "any" function in non variadic manner is possible.
-- multidimensional array is supported
select format('%s, %s', variadic array[['Nazdar','Svete'],['Hello','World']]);
format
-------------------------------
{Nazdar,Svete}, {Hello,World}
(1 row)
Overall, the comments really need to be better. Things like this:
+ /* create short lived copies of fmgr data */ + fmgr_info_copy(&sfinfo, fcinfo->flinfo, fcinfo->flinfo->fn_mcxt); + memcpy(scinfo, fcinfo, sizeof(FunctionCallInfoData)); + scinfo->flinfo = &sfinfo;Really don't cut it, imv. *Why* are we creating a copy of the fmgr
data? Why does it need to be short lived? And what is going to happen
later when you do this?:
pls, see my introduction - in this case FmgrInfo is not immutable (in
this use case), so it is reason for short living copy and then I using
this copied structure instead repeated modification original
structure.
fcinfo = scinfo;
MemoryContextSwitchTo(oldContext);Is there any reason to worry about the fact that fcache->fcinfo_data no
longer matches the fcinfo that we're working on? What if there are
other references made to it in the future? These memcpy's and pointer
foolishness really don't strike me as being a well thought out approach.
fcinfo_data is used for some purposes now - and it can be accessed
from function. Changes that I do are transparent for variadic function
- so I cannot use this.
There are other similar comments throughout which really need to be
rewritten to address why we're doing something, not what is being done-
you can read the code for that.
Thanks for review
Regards
Pavel
Marking this Waiting on Author.
Thanks,
Stephen
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello
2013/1/18 Tom Lane <tgl@sss.pgh.pa.us>:
Stephen Frost <sfrost@snowman.net> writes:
[ quick review of patch ]
On reflection it seems to me that this is probably not a very good
approach overall. Our general theory for functions taking ANY has
been that the core system just computes the arguments and leaves it
to the function to make sense of them. Why should this be different
in the one specific case of VARIADIC ANY with a variadic array?
I am not sure if I understand well to question?
Reason why VARIADIC "any" is different than VARIADIC anyarray is
simple - we have only single type arrays - there are no "any"[] - and
we use combination FunctionCallInfoData structure for data and parser
expression nodes for type specification. And why we use VARIADIC "any"
function? Due our coerce rules - that try to find common coerce type
for any unknown (polymorphic) parameters. This coercion can be
acceptable - and then people can use VARIADIC "anyarray" or
unacceptable - and then people should to use VARIADIC "any" - for
example we would not lost type info for boolean types. Next motivation
for VARIADIC "any" - implementation is very simple and fast - nobody
have to do packing and unpacking array - just push values to narg, arg
and argnull fields of FunctionCallInfoData structure. There are no
necessary any operations with data. There are only one issue - this
corner case.
The approach is also inherently seriously inefficient. Not only
does ExecMakeFunctionResult have to build a separate phony Const
node for each array element, but the variadic function itself can
have no idea that those Consts are all the same type, which means
it's going to do datatype lookup over again for each array element.
(format(), for instance, would have to look up the same type output
function over and over again.) This might not be noticeable on toy
examples with a couple of array entries, but it'll be a large
performance problem on large arrays.
yes, "format()" it actually does it - in all cases. And it is not best
- but almost of overhead is masked by using sys caches.
What is important - for this use case - there is simple and perfect
possible optimization - in this case "non variadic manner call of
variadic "any" function" all variadic parameters will share same type.
Inside variadic function I have not information so this situation is
in this moment, but just I can remember last used type - and I can
reuse it, when parameter type is same like previous parameter.
So there no performance problem.
The patch also breaks any attempt that a variadic function might be
making to cache info about its argument types across calls. I suppose
that the copying of the FmgrInfo is a hack to avoid crashes due to
called functions supposing that their flinfo->fn_extra data is still
valid for the new argument set. Of course that's another pretty
significant performance hit, compounding the per-element hit. Whereas
an ordinary variadic function that is given N arguments in a particular
query will probably do N datatype lookups and cache the info for the
life of the query, a variadic function called with this approach will
do one datatype lookup for each array element in each row of the query;
and there is no way to optimize that.
I believe so cache of last used datatype and related function can be
very effective and enough for this possible performance issues.
But large arrays have a worse problem: the approach flat out does
not work for arrays of more than FUNC_MAX_ARGS elements, because
there's no place to put their values in the FunctionCallInfo struct.
(As submitted, if the array is too big the patch will blithely stomp
all over memory with user-supplied data, making it not merely a crash
risk but probably an exploitable security hole.)
agree - FUNC_MAX_ARGS should be tested - it is significant security
bug and should be fixed.
This last problem is, so far as I can see, unfixable within this
approach; surely we are not going to accept a feature that only works
for small arrays. So I am going to mark the CF item rejected not just
RWF.
disagree - non variadic manner call should not be used for walk around
FUNC_MAX_ARGS limit. So there should not be passed big array.
If somebody need to pass big array to function, then he should to use
usual non variadic function with usual array type parameter. He should
not use a VARIADIC parameter. We didn't design variadic function to
exceeding FUNC_MAX_ARGS limit.
So I strongly disagree with rejection for this argument. By contrast -
a fact so we don't check array size when variadic function is not
called as variadic function is bug.
Any function - varidic or not varidic in any use case have to have max
FUNC_MAX_ARGS arguments. Our internal variadic functions - that are
+/- VARIADIC "any" functions has FUNC_MAX_ARGS limit.
I believe that a workable approach would require having the function
itself act differently when the VARIADIC flag is set. Currently there
is no way for ANY-taking functions to do this because we don't record
the use of the VARIADIC flag in FuncExpr, but it'd be reasonable to
change that, and probably then add a function similar to
get_fn_expr_rettype to dig it out of the FmgrInfo. I don't know if
we could usefully provide any infrastructure beyond that for the case,
but it'd be worth thinking about whether there's any common behavior
for such functions.
You propose now something, what you rejected four months ago.
your proposal is +/- same like my second proposal and implementation
that I sent some time ago. I have not a problem with it - and I'll
rewrite it, just I recapitulate your objection
a) anybody should to fix any existent variadic "any" function
separately - this fix is not general
b) it increase complexity (little bit) for this kind of variadic functions.
please, can we define agreement on strategy how to fix this issue?
I see two important questions?
a) what limits are valid for variadic functions? Now FUNC_MAX_ARGS
limit is ignored sometime - is it ok?
b) where array of variadic parameters for variadic "any" function
should be unnested? two possibilities - executor (increase complexity
of executor) or inside variadic function - (increase complexity of
variadic function).
I wrote three variants how to fix this issue - all variants has some
advantage and some disadvantage - and I believe so fourth and fifth
variant will be same - but all advantage and disadvantage are relative
well defined now - so we should to decide for one way.
No offensive Tom :), sincerely thank you for review
Best regards
Pavel
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Pavel,
While I certainly appreciate your enthusiasm, I don't think this is
going to make it into 9.3, which is what we're currently focused on.
I'd suggest that you put together a wiki page or similar which
outlines how this is going to work and be implemented and it can be
discussed for 9.4 in a couple months. I don't think writing any more
code is going to be helpful for this right now.
Thanks,
Stephen
2013/1/19 Stephen Frost <sfrost@snowman.net>:
Pavel,
While I certainly appreciate your enthusiasm, I don't think this is
going to make it into 9.3, which is what we're currently focused on.I'd suggest that you put together a wiki page or similar which
outlines how this is going to work and be implemented and it can be
discussed for 9.4 in a couple months. I don't think writing any more
code is going to be helpful for this right now.
if we don't define solution now, then probably don't will define
solution for 9.4 too. Moving to next release solves nothing.
Personally, I can living with commiting in 9.4 - people, who use it
and need it, can use existing patch, but I would to have a clean
proposition for this issue, because I spent lot of time on this
relative simple issue - and I am not happy with it. So I would to
write some what will be (probably) commited, and I don't would to
return to this open issue again.
Regards
Pavel
Thanks,
Stephen
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Pavel Stehule <pavel.stehule@gmail.com> writes:
2013/1/18 Tom Lane <tgl@sss.pgh.pa.us>:
The approach is also inherently seriously inefficient. ...
What is important - for this use case - there is simple and perfect
possible optimization - in this case "non variadic manner call of
variadic "any" function" all variadic parameters will share same type.
Inside variadic function I have not information so this situation is
in this moment, but just I can remember last used type - and I can
reuse it, when parameter type is same like previous parameter.
So there no performance problem.
Well, if we have to hack each variadic function to make it work well in
this scenario, that greatly weakens the major argument for the proposed
patch, namely that it provides a single-point fix for VARIADIC behavior.
BTW, I experimented with lobotomizing array_in's caching of I/O function
lookup behavior, by deleting the if-test at arrayfuncs.c line 184. That
seemed to make it about 30% slower for a simple test involving
converting two-element float8 arrays. So while failing to cache this
stuff isn't the end of the world, arguing that it's not worth worrying
about is simply wrong.
But large arrays have a worse problem: the approach flat out does
not work for arrays of more than FUNC_MAX_ARGS elements, because
there's no place to put their values in the FunctionCallInfo struct.
This last problem is, so far as I can see, unfixable within this
approach; surely we are not going to accept a feature that only works
for small arrays. So I am going to mark the CF item rejected not just
RWF.
disagree - non variadic manner call should not be used for walk around
FUNC_MAX_ARGS limit. So there should not be passed big array.
That's utter nonsense. Why wouldn't people expect concat(), for
example, to work for large (or even just moderate-sized) arrays?
This problem *is* a show stopper for this patch. I suggested a way you
can fix it without having such a limitation. If you don't want to go
that way, well, it's not going to happen.
I agree the prospect that each variadic-ANY function would have to deal
with this case for itself is a tad annoying. But there are only two of
them in the existing system, and it's not like a variadic-ANY function
isn't a pretty complicated beast anyway.
You propose now something, what you rejected four months ago.
Well, at the time it wasn't apparent that this approach wouldn't work.
It is now, though.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2013/1/19 Tom Lane <tgl@sss.pgh.pa.us>:
Pavel Stehule <pavel.stehule@gmail.com> writes:
2013/1/18 Tom Lane <tgl@sss.pgh.pa.us>:
The approach is also inherently seriously inefficient. ...
What is important - for this use case - there is simple and perfect
possible optimization - in this case "non variadic manner call of
variadic "any" function" all variadic parameters will share same type.
Inside variadic function I have not information so this situation is
in this moment, but just I can remember last used type - and I can
reuse it, when parameter type is same like previous parameter.So there no performance problem.
Well, if we have to hack each variadic function to make it work well in
this scenario, that greatly weakens the major argument for the proposed
patch, namely that it provides a single-point fix for VARIADIC behavior.BTW, I experimented with lobotomizing array_in's caching of I/O function
lookup behavior, by deleting the if-test at arrayfuncs.c line 184. That
seemed to make it about 30% slower for a simple test involving
converting two-element float8 arrays. So while failing to cache this
stuff isn't the end of the world, arguing that it's not worth worrying
about is simply wrong.But large arrays have a worse problem: the approach flat out does
not work for arrays of more than FUNC_MAX_ARGS elements, because
there's no place to put their values in the FunctionCallInfo struct.
This last problem is, so far as I can see, unfixable within this
approach; surely we are not going to accept a feature that only works
for small arrays. So I am going to mark the CF item rejected not just
RWF.disagree - non variadic manner call should not be used for walk around
FUNC_MAX_ARGS limit. So there should not be passed big array.That's utter nonsense. Why wouldn't people expect concat(), for
example, to work for large (or even just moderate-sized) arrays?This problem *is* a show stopper for this patch. I suggested a way you
can fix it without having such a limitation. If you don't want to go
that way, well, it's not going to happen.
I agree the prospect that each variadic-ANY function would have to deal
with this case for itself is a tad annoying. But there are only two of
them in the existing system, and it's not like a variadic-ANY function
isn't a pretty complicated beast anyway.You propose now something, what you rejected four months ago.
Well, at the time it wasn't apparent that this approach wouldn't work.
It is now, though.
I have no problem rewrite patch, I'll send new version early.
Regards
Pavel
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Jan 19, 2013 at 11:58 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:
We introduced VARIADIC "any" function. Motivation for this kind of
function was bypassing postgresql's coerce rules - and own rules
implementation for requested functionality. Some builtins function
does it internally - but it is not possible for custom functions or it
is not possible without some change in parser. Same motivation is
reason why "format" function is VARIADIC "any" function.
I'd just like to draw the attention of all assembled to the fact that
this is another example of the cottage industry we've created in
avoiding our own burdensome typecasting rules. I not long ago
proposed a patch that went nowhere which would have obviated the need
for this sort of nonsense in a much more principled way, which of
course went nowhere, despite the design being one which Tom himself
proposed. Is there any amount of this which will sway popular opinion
to the point of view that the problem is not with the individual
cases, but the rules themselves?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Jan 19, 2013 at 3:27 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
disagree - non variadic manner call should not be used for walk around
FUNC_MAX_ARGS limit. So there should not be passed big array.That's utter nonsense. Why wouldn't people expect concat(), for
example, to work for large (or even just moderate-sized) arrays?
/me blinks.
What does that have to do with anything? IIUC, the question isn't
whether CONCAT() would work for large arrays, but rather for very
large numbers of arrays written out as CONCAT(a1, ..., a10000000).
I don't understand why an appropriately-placed check against
FUNC_MAX_ARGS does anything other than enforce a limit we already
have. Or are we currently not consistently enforcing that limit?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
On Sat, Jan 19, 2013 at 3:27 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
That's utter nonsense. Why wouldn't people expect concat(), for
example, to work for large (or even just moderate-sized) arrays?
/me blinks.
What does that have to do with anything? IIUC, the question isn't
whether CONCAT() would work for large arrays, but rather for very
large numbers of arrays written out as CONCAT(a1, ..., a10000000).
No, the question is what happens with CONCAT(VARIADIC some-array-here),
which currently just returns the array as-is, but which really ought
to concat all the array elements as if they'd been separate arguments.
Pavel is claiming it's okay for that to fall over if the array has
more than 100 elements. I disagree, not only for the specific case of
CONCAT(), but with the more general implication that such a limitation
is going to be okay for any VARIADIC ANY function that anyone will ever
write.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 01/20/2013 01:37 PM, Robert Haas wrote:
On Sat, Jan 19, 2013 at 11:58 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:
We introduced VARIADIC "any" function. Motivation for this kind of
function was bypassing postgresql's coerce rules - and own rules
implementation for requested functionality. Some builtins function
does it internally - but it is not possible for custom functions or it
is not possible without some change in parser. Same motivation is
reason why "format" function is VARIADIC "any" function.I'd just like to draw the attention of all assembled to the fact that
this is another example of the cottage industry we've created in
avoiding our own burdensome typecasting rules. I not long ago
proposed a patch that went nowhere which would have obviated the need
for this sort of nonsense in a much more principled way, which of
course went nowhere, despite the design being one which Tom himself
proposed. Is there any amount of this which will sway popular opinion
to the point of view that the problem is not with the individual
cases, but the rules themselves?
Uh, reference please? This doesn't jog my memory despite it being
something that's obviously interesting in light of my recent work. (That
could be a a case of dying synapses too.)
cheers
andrew
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello
2013/1/20 Tom Lane <tgl@sss.pgh.pa.us>:
Robert Haas <robertmhaas@gmail.com> writes:
On Sat, Jan 19, 2013 at 3:27 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
That's utter nonsense. Why wouldn't people expect concat(), for
example, to work for large (or even just moderate-sized) arrays?/me blinks.
What does that have to do with anything? IIUC, the question isn't
whether CONCAT() would work for large arrays, but rather for very
large numbers of arrays written out as CONCAT(a1, ..., a10000000).No, the question is what happens with CONCAT(VARIADIC some-array-here),
which currently just returns the array as-is, but which really ought
to concat all the array elements as if they'd been separate arguments.Pavel is claiming it's okay for that to fall over if the array has
more than 100 elements. I disagree, not only for the specific case of
CONCAT(), but with the more general implication that such a limitation
is going to be okay for any VARIADIC ANY function that anyone will ever
write.
I am sending patch that is based on last Tom's proposal
it missing some small fixes for other variadic "any" functions concat,
concat_ws - I'll send it tomorrow
Regards
Pavel
Show quoted text
regards, tom lane
Attachments:
variadic_any_fix.patchapplication/octet-stream; name=variadic_any_fix.patchDownload
*** 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);
Robert Haas <robertmhaas@gmail.com> writes:
On Sat, Jan 19, 2013 at 11:58 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:
We introduced VARIADIC "any" function. Motivation for this kind of
function was bypassing postgresql's coerce rules - and own rules
implementation for requested functionality. Some builtins function
does it internally - but it is not possible for custom functions or it
is not possible without some change in parser. Same motivation is
reason why "format" function is VARIADIC "any" function.
I'd just like to draw the attention of all assembled to the fact that
this is another example of the cottage industry we've created in
avoiding our own burdensome typecasting rules.
I suppose this complaint is based on the idea that we could have
declared format() as format(fmt text, VARIADIC values text[]) if
only the argument matching rules were sufficiently permissive.
I disagree with that though. For that to be anywhere near equivalent,
we would have to allow argument matching to cast any data type to
text, even if the corresponding cast were explicit-only. Surely
that is too dangerous to consider. Even then it wouldn't be quite
equivalent, because format() will work on any type whether or not
there is a cast to text at all (since it relies on calling the type
I/O functions instead).
While VARIADIC ANY functions are surely a bit of a hack, I think they
are a better solution than destroying the type system's ability to check
function calls at all. At least the risks are confined to those
specific functions, and not any function anywhere.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sun, Jan 20, 2013 at 2:26 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
On Sat, Jan 19, 2013 at 3:27 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
That's utter nonsense. Why wouldn't people expect concat(), for
example, to work for large (or even just moderate-sized) arrays?/me blinks.
What does that have to do with anything? IIUC, the question isn't
whether CONCAT() would work for large arrays, but rather for very
large numbers of arrays written out as CONCAT(a1, ..., a10000000).No, the question is what happens with CONCAT(VARIADIC some-array-here),
which currently just returns the array as-is, but which really ought
to concat all the array elements as if they'd been separate arguments.
Wow, that's pretty surprising behavior.
Pavel is claiming it's okay for that to fall over if the array has
more than 100 elements. I disagree, not only for the specific case of
CONCAT(), but with the more general implication that such a limitation
is going to be okay for any VARIADIC ANY function that anyone will ever
write.
I don't know - how many of those will there really ever be? I mean,
people only write functions as VARIADIC as a notational convenience,
don't they? If you actually need to pass more than 100 separate
pieces of data to a function, sending over 100+ parameters is almost
certainly the Wrong Way To Do It.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sun, Jan 20, 2013 at 2:35 PM, Andrew Dunstan <andrew@dunslane.net> wrote:
Uh, reference please? This doesn't jog my memory despite it being something
that's obviously interesting in light of my recent work. (That could be a a
case of dying synapses too.)
/messages/by-id/CA+TgmoZLm6Kp77HXEeU_6B_OBGEnWm9TaGrDF4SrPnsvyEvw2A@mail.gmail.com
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sun, Jan 20, 2013 at 3:21 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I suppose this complaint is based on the idea that we could have
declared format() as format(fmt text, VARIADIC values text[]) if
only the argument matching rules were sufficiently permissive.
I disagree with that though. For that to be anywhere near equivalent,
we would have to allow argument matching to cast any data type to
text, even if the corresponding cast were explicit-only. Surely
that is too dangerous to consider. Even then it wouldn't be quite
equivalent, because format() will work on any type whether or not
there is a cast to text at all (since it relies on calling the type
I/O functions instead).
Well, as previously discussed a number of times, all types are
considered to have assignment casts to text via IO whether or not
there is an entry in pg_cast. So the only case in which my proposal
would have failed to make this work is if someone added an entry in
pg_cast and tagged it as an explicit cast. I can't quite imagine what
sort of situation might justify such a perplexing step, but if someone
does it and it breaks this then I think they're getting what they paid
for. So I think it's pretty close to equivalent.
While VARIADIC ANY functions are surely a bit of a hack, I think they
are a better solution than destroying the type system's ability to check
function calls at all. At least the risks are confined to those
specific functions, and not any function anywhere.
I think this is hyperbole which ignores the facts on the ground. Over
and over again, we've seen examples of users who are perplexed because
there's only one function called wumpus() and we won't call it due to
some perceived failure of the types to match sufficiently closely.
Destroying the type system's ability to needlessly reject
*unambiguous* calls is, IMHO, a feature, not a bug.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2013/1/20 Robert Haas <robertmhaas@gmail.com>:
On Sun, Jan 20, 2013 at 2:26 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
On Sat, Jan 19, 2013 at 3:27 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
That's utter nonsense. Why wouldn't people expect concat(), for
example, to work for large (or even just moderate-sized) arrays?/me blinks.
What does that have to do with anything? IIUC, the question isn't
whether CONCAT() would work for large arrays, but rather for very
large numbers of arrays written out as CONCAT(a1, ..., a10000000).No, the question is what happens with CONCAT(VARIADIC some-array-here),
which currently just returns the array as-is, but which really ought
to concat all the array elements as if they'd been separate arguments.Wow, that's pretty surprising behavior.
Pavel is claiming it's okay for that to fall over if the array has
more than 100 elements. I disagree, not only for the specific case of
CONCAT(), but with the more general implication that such a limitation
is going to be okay for any VARIADIC ANY function that anyone will ever
write.
one correction - I would to raise error, if array is larger than
FUNC_MAX_ARGS. But is true, so this limit is for VARIADIC function
synthetic, because parameters are passed in array. On second hand this
is inconsistency.
I don't know - how many of those will there really ever be? I mean,
people only write functions as VARIADIC as a notational convenience,
don't they? If you actually need to pass more than 100 separate
pieces of data to a function, sending over 100+ parameters is almost
certainly the Wrong Way To Do It.--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2013/1/20 Robert Haas <robertmhaas@gmail.com>:
On Sun, Jan 20, 2013 at 3:21 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I suppose this complaint is based on the idea that we could have
declared format() as format(fmt text, VARIADIC values text[]) if
only the argument matching rules were sufficiently permissive.
I disagree with that though. For that to be anywhere near equivalent,
we would have to allow argument matching to cast any data type to
text, even if the corresponding cast were explicit-only. Surely
that is too dangerous to consider. Even then it wouldn't be quite
equivalent, because format() will work on any type whether or not
there is a cast to text at all (since it relies on calling the type
I/O functions instead).Well, as previously discussed a number of times, all types are
considered to have assignment casts to text via IO whether or not
there is an entry in pg_cast. So the only case in which my proposal
would have failed to make this work is if someone added an entry in
pg_cast and tagged it as an explicit cast. I can't quite imagine what
sort of situation might justify such a perplexing step, but if someone
does it and it breaks this then I think they're getting what they paid
for. So I think it's pretty close to equivalent.While VARIADIC ANY functions are surely a bit of a hack, I think they
are a better solution than destroying the type system's ability to check
function calls at all. At least the risks are confined to those
specific functions, and not any function anywhere.I think this is hyperbole which ignores the facts on the ground. Over
and over again, we've seen examples of users who are perplexed because
there's only one function called wumpus() and we won't call it due to
some perceived failure of the types to match sufficiently closely.
Destroying the type system's ability to needlessly reject
*unambiguous* calls is, IMHO, a feature, not a bug.
I am thinking so VARIADIC ANY functions is only one possible solution
for functions with more complex coercion rules like oracle DECODE
function. Just modification coercion rules is not enough - or we need
to thinking about extensible parser and analyser.
Regards
Pavel
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
On Sun, Jan 20, 2013 at 2:26 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Pavel is claiming it's okay for that to fall over if the array has
more than 100 elements. I disagree, not only for the specific case of
CONCAT(), but with the more general implication that such a limitation
is going to be okay for any VARIADIC ANY function that anyone will ever
write.
I don't know - how many of those will there really ever be? I mean,
people only write functions as VARIADIC as a notational convenience,
don't they? If you actually need to pass more than 100 separate
pieces of data to a function, sending over 100+ parameters is almost
certainly the Wrong Way To Do It.
Well, not necessarily, if they're reasonably expressed as an array.
I would also point out that there is no corresponding limitation on
variadic functions that take any type other than ANY. Indeed, despite
Pavel's claim to the contrary, I'm pretty sure it's seen as a feature
that there's no specific upper limit to how many parameters you can pass
to a variadic function when using the "VARIADIC array-value" syntax.
It's certainly a feature that you can pass a varying number of
parameters that way, thereby "evading" the syntactic fact that you can't
pass a varying number of parameters any other way. I don't see how
come it isn't a feature that you can "evade" the FUNC_MAX_ARGS limit
that way, or why we'd consider it acceptable for variably-sized
parameter arrays to have such a small arbitrary limit.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 01/21/2013 02:37 AM, Robert Haas wrote:
On Sat, Jan 19, 2013 at 11:58 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:
We introduced VARIADIC "any" function. Motivation for this kind of
function was bypassing postgresql's coerce rules - and own rules
implementation for requested functionality. Some builtins function
does it internally - but it is not possible for custom functions or it
is not possible without some change in parser. Same motivation is
reason why "format" function is VARIADIC "any" function.I'd just like to draw the attention of all assembled to the fact that
this is another example of the cottage industry we've created in
avoiding our own burdensome typecasting rules. I not long ago
proposed a patch that went nowhere which would have obviated the need
for this sort of nonsense in a much more principled way, which of
course went nowhere, despite the design being one which Tom himself
proposed. Is there any amount of this which will sway popular opinion
to the point of view that the problem is not with the individual
cases, but the rules themselves?
FWIW, I find PostgreSQL's type casting rules excessively strict and very
painful, especially when working via query generation layers and ORMs
with pseudo-text data types like "xml" and "json". I'd rather work with
direct SQL, but that's not always an option.
The fact that this works:
regress=> CREATE TABLE castdemo(x xml);
CREATE TABLE
regress=> INSERT INTO castdemo(x) VALUES ('<element/>');
INSERT 0 1
but there's no way to express it via a parameterized insert unless you
know that the field type is "xml" is frustrating. There's no "unknown"
type-placeholder in prepared statements, and we'd never get client
interfaces to use one if there was. I almost invariably create implicit
casts from text to xml and json so that this works:
regress=> PREPARE paraminsert(text) AS INSERT INTO castdemo(x) VALUES ($1);
instead of failing with:
regress=> PREPARE paraminsert(text) AS INSERT INTO castdemo(x) VALUES ($1);
ERROR: column "x" is of type xml but expression is of type text
LINE 1: ...RE paraminsert(text) AS INSERT INTO castdemo(x) VALUES ($1);
^
HINT: You will need to rewrite or cast the expression.
JDBC users in particular will find the strict refusal to convert "text"
to "xml" or "json" to be very frustrating. The JDBC driver has - AFAIK -
no way to ask the server "In the statement INSERT INTO castdemo(x)
VALUES ($1) what data type do you expect for '$1'" ... nor would it need
one if the server weren't so strict about these casts.
--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-general mailing list (pgsql-general@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-general
On Sun, Jan 20, 2013 at 10:01 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
On Sun, Jan 20, 2013 at 2:26 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Pavel is claiming it's okay for that to fall over if the array has
more than 100 elements. I disagree, not only for the specific case of
CONCAT(), but with the more general implication that such a limitation
is going to be okay for any VARIADIC ANY function that anyone will ever
write.I don't know - how many of those will there really ever be? I mean,
people only write functions as VARIADIC as a notational convenience,
don't they? If you actually need to pass more than 100 separate
pieces of data to a function, sending over 100+ parameters is almost
certainly the Wrong Way To Do It.Well, not necessarily, if they're reasonably expressed as an array.
I would also point out that there is no corresponding limitation on
variadic functions that take any type other than ANY. Indeed, despite
Pavel's claim to the contrary, I'm pretty sure it's seen as a feature
that there's no specific upper limit to how many parameters you can pass
to a variadic function when using the "VARIADIC array-value" syntax.
It's certainly a feature that you can pass a varying number of
parameters that way, thereby "evading" the syntactic fact that you can't
pass a varying number of parameters any other way. I don't see how
come it isn't a feature that you can "evade" the FUNC_MAX_ARGS limit
that way, or why we'd consider it acceptable for variably-sized
parameter arrays to have such a small arbitrary limit.
OK, I see. If people are already counting on there being no fixed
limit for variadic functions with a type other than "any", then it
would indeed seem weird to make "any" an exception. I'm not sure how
much practical use case there is for such a thing, but still.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sun, Jan 20, 2013 at 7:15 PM, Craig Ringer <craig@2ndquadrant.com> wrote:
FWIW, I find PostgreSQL's type casting rules excessively strict and very
painful, especially when working via query generation layers and ORMs
with pseudo-text data types like "xml" and "json". I'd rather work with
direct SQL, but that's not always an option.
I want to second this specifically with regard to XML and JSON. I
understand the concern about implicit casts to text, but what about
bundling with a built-in domain that pseudo-text types can be implicitly
cast to? Something like a generaltext type, where casts from other
pseudo-text types are implicitly cast?
hmmm might be worth playing around with this in extension format.....
Best Wishes,
Chris Travers
2013/1/21 Robert Haas <robertmhaas@gmail.com>:
On Sun, Jan 20, 2013 at 10:01 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
On Sun, Jan 20, 2013 at 2:26 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Pavel is claiming it's okay for that to fall over if the array has
more than 100 elements. I disagree, not only for the specific case of
CONCAT(), but with the more general implication that such a limitation
is going to be okay for any VARIADIC ANY function that anyone will ever
write.I don't know - how many of those will there really ever be? I mean,
people only write functions as VARIADIC as a notational convenience,
don't they? If you actually need to pass more than 100 separate
pieces of data to a function, sending over 100+ parameters is almost
certainly the Wrong Way To Do It.Well, not necessarily, if they're reasonably expressed as an array.
I would also point out that there is no corresponding limitation on
variadic functions that take any type other than ANY. Indeed, despite
Pavel's claim to the contrary, I'm pretty sure it's seen as a feature
that there's no specific upper limit to how many parameters you can pass
to a variadic function when using the "VARIADIC array-value" syntax.
It's certainly a feature that you can pass a varying number of
parameters that way, thereby "evading" the syntactic fact that you can't
pass a varying number of parameters any other way. I don't see how
come it isn't a feature that you can "evade" the FUNC_MAX_ARGS limit
that way, or why we'd consider it acceptable for variably-sized
parameter arrays to have such a small arbitrary limit.OK, I see. If people are already counting on there being no fixed
limit for variadic functions with a type other than "any", then it
would indeed seem weird to make "any" an exception. I'm not sure how
much practical use case there is for such a thing, but still.
after sleeping and some thinking about topic - yes - Tom opinion is
correct too - theoretically we can count all variadic argument as one.
Exception is just VARIADIC "any" when is called usually - it can be
only better documented - I don't see a problem now
Regards
Pavel
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello
2013/1/19 Tom Lane <tgl@sss.pgh.pa.us>:
Pavel Stehule <pavel.stehule@gmail.com> writes:
2013/1/18 Tom Lane <tgl@sss.pgh.pa.us>:
The approach is also inherently seriously inefficient. ...
What is important - for this use case - there is simple and perfect
possible optimization - in this case "non variadic manner call of
variadic "any" function" all variadic parameters will share same type.
Inside variadic function I have not information so this situation is
in this moment, but just I can remember last used type - and I can
reuse it, when parameter type is same like previous parameter.So there no performance problem.
Well, if we have to hack each variadic function to make it work well in
this scenario, that greatly weakens the major argument for the proposed
patch, namely that it provides a single-point fix for VARIADIC behavior.BTW, I experimented with lobotomizing array_in's caching of I/O function
lookup behavior, by deleting the if-test at arrayfuncs.c line 184. That
seemed to make it about 30% slower for a simple test involving
converting two-element float8 arrays. So while failing to cache this
stuff isn't the end of the world, arguing that it's not worth worrying
about is simply wrong.But large arrays have a worse problem: the approach flat out does
not work for arrays of more than FUNC_MAX_ARGS elements, because
there's no place to put their values in the FunctionCallInfo struct.
This last problem is, so far as I can see, unfixable within this
approach; surely we are not going to accept a feature that only works
for small arrays. So I am going to mark the CF item rejected not just
RWF.disagree - non variadic manner call should not be used for walk around
FUNC_MAX_ARGS limit. So there should not be passed big array.That's utter nonsense. Why wouldn't people expect concat(), for
example, to work for large (or even just moderate-sized) arrays?This problem *is* a show stopper for this patch. I suggested a way you
can fix it without having such a limitation. If you don't want to go
that way, well, it's not going to happen.I agree the prospect that each variadic-ANY function would have to deal
with this case for itself is a tad annoying. But there are only two of
them in the existing system, and it's not like a variadic-ANY function
isn't a pretty complicated beast anyway.You propose now something, what you rejected four months ago.
Well, at the time it wasn't apparent that this approach wouldn't work.
It is now, though.
so here is rewritten patch
* there are no limit for array size that holds variadic arguments
* iteration over mentioned array is moved to variadic function implementation
* there is a new function get_fn_expr_arg_variadic, that returns
true, when last argument has label VARIADIC - via FuncExpr node
* fix all our variadic "any" functions - concat(), concat_ws() and format()
* there is a small optimization - remember last used typOutput
function and reuse it when possible
* it doesn't change any previous test or documented behave
I hope so almost all issues and questions are solved and there are
agreement on implemented behave.
Regards
Pavel
Show quoted text
regards, tom lane
Attachments:
variadic_any_fix_20130121.patchapplication/octet-stream; name=variadic_any_fix_20130121.patchDownload
*** 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 text_format_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,
***************
*** 3789,3794 **** string_agg_finalfn(PG_FUNCTION_ARGS)
--- 3791,3920 ----
}
/*
+ * Holds state data for iteration over concat() and concat_ws() parameters
+ */
+ typedef struct ConcatIteratorData
+ {
+ bool funcvariadic; /* true when last parameter is labeled VARIADIC */
+ FunctionCallInfo fcinfo;
+ int argidx;
+ int nargs;
+ ArrayIterator iterator;
+ Oid vararg_typid;
+ } ConcatIteratorData;
+
+ /*
+ * Initialise concat function argument iterator
+ *
+ * Functions concat() and concat_ws() are variadic "any" functions, so arguments
+ * can be passed directly in fcinfo->arg array or in array passed as first (concat())
+ * or second (concat_ws()) fcinfo->arg field. Iterator hides differences between
+ * both variants.
+ */
+ static void
+ initConcatIterator(ConcatIteratorData *ci, FunctionCallInfo fcinfo, int argidx)
+ {
+ MemSet(ci, 0, sizeof(ConcatIteratorData));
+
+ if (get_fn_expr_arg_variadic(fcinfo->flinfo))
+ {
+ Oid arr_typid;
+ ArrayType *arr;
+ int slice;
+
+ /* 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;
+
+ ci->vararg_typid = slice > 0 ? arr_typid : ARR_ELEMTYPE(arr);
+ ci->iterator = array_create_iterator(arr, slice);
+ ci->funcvariadic = true;
+ }
+ else
+ {
+ ci->argidx = argidx;
+ ci->fcinfo = fcinfo;
+ ci->nargs = PG_NARGS();
+ ci->funcvariadic = false;
+ }
+ }
+
+ /*
+ * set value of next concat*() function argument
+ *
+ * returns false when all parametes was processed
+ */
+ static bool
+ ConcatIteratorIterate(ConcatIteratorData *ci, Datum *value, bool *isnull, Oid *typid)
+ {
+ if (ci->funcvariadic)
+ {
+ Assert(ci->iterator != NULL);
+
+ /* get next array iterator field */
+ if (array_iterate(ci->iterator, value, isnull))
+ {
+ *typid = ci->vararg_typid;
+
+ return true;
+ }
+ }
+ else
+ {
+ /* get next FunctionCallInfo argument */
+ if (ci->argidx < ci->nargs)
+ {
+ if (!ci->fcinfo->argnull[ci->argidx])
+ {
+ *value = ci->fcinfo->arg[ci->argidx];
+ *typid = get_fn_expr_argtype(ci->fcinfo->flinfo, ci->argidx);
+ *isnull = false;
+ }
+ else
+ *isnull = true;
+
+ ci->argidx += 1;
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /*
+ * release array iterator if was used
+ */
+ static void
+ ConcatIteratorFree(ConcatIteratorData *ci)
+ {
+ if (ci->funcvariadic)
+ {
+ Assert(ci->iterator != NULL);
+
+ /* Release iterator */
+ array_free_iterator(ci->iterator);
+ }
+ }
+
+ /*
* Implementation of both concat() and concat_ws().
*
* sepstr/seplen describe the separator. argidx is the first argument
***************
*** 3801,3818 **** concat_internal(const char *sepstr, int seplen, int argidx,
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)
--- 3927,3950 ----
text *result;
StringInfoData str;
bool first_arg = true;
! Datum value;
! bool isnull;
! Oid valtype;
! Oid prev_typid = InvalidOid;
! Oid typOutput = InvalidOid;
! ConcatIteratorData ci;
initStringInfo(&str);
+ initConcatIterator(&ci, fcinfo, argidx);
! /* copy data from iterator */
! while (ConcatIteratorIterate(&ci, &value, &isnull, &valtype))
{
! if (!isnull)
{
! /* valtype must be defined */
! if (!OidIsValid(valtype))
! elog(ERROR, "could not determine data type of concat() input");
/* add separator if appropriate */
if (first_arg)
***************
*** 3820,3837 **** concat_internal(const char *sepstr, int seplen, int argidx,
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;
}
--- 3952,3974 ----
else
appendBinaryStringInfo(&str, sepstr, seplen);
! /* search typ output function when cannot to use previous */
! if (!OidIsValid(typOutput) || prev_typid != valtype)
! {
! bool typIsVarlena;
!
! getTypeOutputInfo(valtype, &typOutput, &typIsVarlena);
! prev_typid = valtype;
! }
!
appendStringInfoString(&str,
! OidOutputFunctionCall(typOutput, value));
}
}
result = cstring_to_text_with_len(str.data, str.len);
pfree(str.data);
+ ConcatIteratorFree(&ci);
return result;
}
***************
*** 3959,3964 **** text_format(PG_FUNCTION_ARGS)
--- 4096,4108 ----
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)
--- 4114,4137 ----
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;
+
+ text_format_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")));
--- 4224,4230 ----
}
/* 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,
--- 4234,4267 ----
* 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. */
--- 4275,4293 ----
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. */
--- 4303,4308 ----
***************
*** 4145,4150 **** text_format_string_conversion(StringInfo buf, char conversion,
--- 4327,4401 ----
}
/*
+ * unpack VARIADIC parameter - used by VARIADIC "any" functions, when
+ * parameter is labeled as VARIADIC
+ */
+ static void
+ text_format_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
***************
*** 136,141 **** select quote_literal(e'\\');
--- 136,159 ----
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)
+
+ --should fail
+ select concat_ws(',', variadic 10);
+ ERROR: variadic argument should be an array
+ select concat_ws(',', variadic NULL);
+ ERROR: variadic argument cannot be null
/*
* format
*/
***************
*** 241,243 **** select format('Hello %s %s, %2$s %2$s', 'World', 'Hello again');
--- 259,314 ----
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
***************
*** 44,49 **** select i, left('ahoj', i), right('ahoj', i) from generate_series(-5, 5) t(i) ord
--- 44,55 ----
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]);
+ --should fail
+ select concat_ws(',', variadic 10);
+ select concat_ws(',', variadic NULL);
/*
* format
***************
*** 76,78 **** select format('%1$1', 1);
--- 82,101 ----
--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);
Pavel Stehule <pavel.stehule@gmail.com> writes:
so here is rewritten patch
I've applied the infrastructure parts of this, but not the changes
to format() and concat().
Why are the format and concat patches so randomly different?
Not only is the internal implementation completely different for no
obvious reason, but the user-visible behavior is inconsistent too.
You've got one of them iterating over elements and one over slices.
That seems pretty bogus. Personally I'd vote for the element-level
behavior in both cases, because that's generally what happens in other
PG functions when there's no particular reason to pay attention to the
structure of a multidimensional array. I certainly don't see a reason
why they should be making opposite choices.
Also, I'm not sure that it's appropriate to throw an error if the given
argument is null or not an array. Previous versions did not throw an
error in such cases. Perhaps just fall back to behaving as if it
weren't marked VARIADIC? You could possibly make an argument for
not-an-array-type being an error, since that's a statically inconsistent
type situation, but I really don't like a null value being an error.
A function could easily receive a null value for an array parameter
that it wants to pass on to format() or concat().
BTW, using array_iterate as a substitute for deconstruct_array is
neither efficient nor good style. array_iterate is for processing the
values as you scan the array.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2013/1/22 Tom Lane <tgl@sss.pgh.pa.us>:
Pavel Stehule <pavel.stehule@gmail.com> writes:
so here is rewritten patch
I've applied the infrastructure parts of this, but not the changes
to format() and concat().Why are the format and concat patches so randomly different?
Not only is the internal implementation completely different for no
obvious reason, but the user-visible behavior is inconsistent too.
You've got one of them iterating over elements and one over slices.
That seems pretty bogus. Personally I'd vote for the element-level
behavior in both cases, because that's generally what happens in other
PG functions when there's no particular reason to pay attention to the
structure of a multidimensional array. I certainly don't see a reason
why they should be making opposite choices.
some months ago, there was a one argument against this patch (or this
feature) impossibility to pass array as one argument into variadic
function. I am thinking so natural reply is a slicing.
Without slicing you cannot to pass array as a argument - so it is
relative hard limit. But if I thinking about it - not too much people
use it with multidimensional array, so I prefer slicing as natural
behave, but I can live without it.
What do you thinking about limit to just only one dimensional arrays?
- then we don't need solve this question now. This behave is important
for format() -- and maybe it is premature optimization - but I don't
to close these doors too early.
Also, I'm not sure that it's appropriate to throw an error if the given
argument is null or not an array. Previous versions did not throw an
error in such cases. Perhaps just fall back to behaving as if it
weren't marked VARIADIC? You could possibly make an argument for
not-an-array-type being an error, since that's a statically inconsistent
type situation, but I really don't like a null value being an error.
A function could easily receive a null value for an array parameter
that it wants to pass on to format() or concat().
I did test with custom variadic function
CREATE OR REPLACE FUNCTION public.ileast(VARIADIC integer[])
RETURNS integer
LANGUAGE sql
AS $function$
select max(v) from unnest($1) g(v)
$function$
postgres=# select ileast(10,20);
ileast
--------
20
(1 row)
postgres=# select ileast(variadic null);
ileast
--------
(1 row)
postgres=# select ileast(variadic 10);
ERROR: function ileast(integer) does not exist
LINE 1: select ileast(variadic 10);
^
HINT: No function matches the given name and argument types. You
might need to add explicit type casts.
so NULL should be supported, and ARRAY should be requested.
question is about level - WARNING or ERROR - in long term perspective
I am sure so ERROR is correct.
If we raise only WARNING - then what we should to do? Ignore VARIADIC
label like before or return correct value? Then I don't see a sense
for WARNING - this is possible incompatibility :( - but better fix it
early than later
Other opinions?
BTW, using array_iterate as a substitute for deconstruct_array is
neither efficient nor good style. array_iterate is for processing the
values as you scan the array.
yes - some deconstruct_sliced_array will be better and cleaner.
Depends how we decide first question about using multidimensional
arrays. Probably would not be necessary.
Regards
Pavel
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello
2013/1/22 Tom Lane <tgl@sss.pgh.pa.us>:
Pavel Stehule <pavel.stehule@gmail.com> writes:
so here is rewritten patch
I've applied the infrastructure parts of this, but not the changes
to format() and concat().Why are the format and concat patches so randomly different?
Not only is the internal implementation completely different for no
obvious reason, but the user-visible behavior is inconsistent too.
You've got one of them iterating over elements and one over slices.
That seems pretty bogus. Personally I'd vote for the element-level
behavior in both cases, because that's generally what happens in other
PG functions when there's no particular reason to pay attention to the
structure of a multidimensional array. I certainly don't see a reason
why they should be making opposite choices.Also, I'm not sure that it's appropriate to throw an error if the given
argument is null or not an array. Previous versions did not throw an
error in such cases. Perhaps just fall back to behaving as if it
weren't marked VARIADIC? You could possibly make an argument for
not-an-array-type being an error, since that's a statically inconsistent
type situation, but I really don't like a null value being an error.
A function could easily receive a null value for an array parameter
that it wants to pass on to format() or concat().BTW, using array_iterate as a substitute for deconstruct_array is
neither efficient nor good style. array_iterate is for processing the
values as you scan the array.
I updated patch
* simplify concat and concat_ws with reuse array_to_text_internal
* remove support for slicing (multidimensional arrays)
* VARIADIC NULL is allowed
Regards
Pavel
Show quoted text
regards, tom lane
Attachments:
variadic_any_fix_20130122.patchapplication/octet-stream; name=variadic_any_fix_20130122.patchDownload
*** 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.
*/
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")));
!
! /* allow multidimensional arrays - use slicing when array is multidimensional */
! 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,4077 ----
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;
+
+ /* do nothing, when VARIADIC argument is NULL */
+ if (PG_ARGISNULL(1))
+ PG_RETURN_NULL();
+
+ /* 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")));
--- 4170,4176 ----
}
/* 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,
--- 4180,4213 ----
* 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. */
--- 4221,4239 ----
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. */
--- 4249,4254 ----
*** a/src/test/regress/expected/text.out
--- b/src/test/regress/expected/text.out
***************
*** 136,141 **** select quote_literal(e'\\');
--- 136,169 ----
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);
+ concat
+ --------
+
+ (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');
--- 269,317 ----
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)
+
+ -- 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,56 ----
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);
+ --should fail
+ select concat_ws(',', variadic 10);
/*
* format
***************
*** 76,78 **** select format('%1$1', 1);
--- 83,99 ----
--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]);
+
+ -- 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);
sorry
I have to change wrong comment
Regards
Pavel
2013/1/22 Pavel Stehule <pavel.stehule@gmail.com>:
Show quoted text
Hello
2013/1/22 Tom Lane <tgl@sss.pgh.pa.us>:
Pavel Stehule <pavel.stehule@gmail.com> writes:
so here is rewritten patch
I've applied the infrastructure parts of this, but not the changes
to format() and concat().Why are the format and concat patches so randomly different?
Not only is the internal implementation completely different for no
obvious reason, but the user-visible behavior is inconsistent too.
You've got one of them iterating over elements and one over slices.
That seems pretty bogus. Personally I'd vote for the element-level
behavior in both cases, because that's generally what happens in other
PG functions when there's no particular reason to pay attention to the
structure of a multidimensional array. I certainly don't see a reason
why they should be making opposite choices.Also, I'm not sure that it's appropriate to throw an error if the given
argument is null or not an array. Previous versions did not throw an
error in such cases. Perhaps just fall back to behaving as if it
weren't marked VARIADIC? You could possibly make an argument for
not-an-array-type being an error, since that's a statically inconsistent
type situation, but I really don't like a null value being an error.
A function could easily receive a null value for an array parameter
that it wants to pass on to format() or concat().BTW, using array_iterate as a substitute for deconstruct_array is
neither efficient nor good style. array_iterate is for processing the
values as you scan the array.I updated patch
* simplify concat and concat_ws with reuse array_to_text_internal
* remove support for slicing (multidimensional arrays)
* VARIADIC NULL is allowedRegards
Pavel
regards, tom lane
Attachments:
variadic_any_fix_20130122.patchapplication/octet-stream; name=variadic_any_fix_20130122.patchDownload
*** 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.
*/
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,4077 ----
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;
+
+ /* do nothing, when VARIADIC argument is NULL */
+ if (PG_ARGISNULL(1))
+ PG_RETURN_NULL();
+
+ /* 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")));
--- 4170,4176 ----
}
/* 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,
--- 4180,4213 ----
* 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. */
--- 4221,4239 ----
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. */
--- 4249,4254 ----
*** a/src/test/regress/expected/text.out
--- b/src/test/regress/expected/text.out
***************
*** 136,141 **** select quote_literal(e'\\');
--- 136,169 ----
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);
+ concat
+ --------
+
+ (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');
--- 269,317 ----
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)
+
+ -- 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,56 ----
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);
+ --should fail
+ select concat_ws(',', variadic 10);
/*
* format
***************
*** 76,78 **** select format('%1$1', 1);
--- 83,99 ----
--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]);
+
+ -- 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);
Hello
I sent a updated patch, but still I am not sure in one topic
Also, I'm not sure that it's appropriate to throw an error if the given
argument is null or not an array. Previous versions did not throw an
error in such cases. Perhaps just fall back to behaving as if it
weren't marked VARIADIC? You could possibly make an argument for
not-an-array-type being an error, since that's a statically inconsistent
type situation, but I really don't like a null value being an error.
A function could easily receive a null value for an array parameter
that it wants to pass on to format() or concat().
what should be result of concat(variadic NULL::int[])
I enabled this use case, but what should be result?
usually concat() function needs one parameter as minimum and then
returns empty string or some string. But concat(variadic NULL::int[])
is +/- zero parameters call. A result should be empty string or NULL?
I am vote returning NULL and I have a only one argument
If concat(variadic NULL::int[]) returns NULL, then it returns
different "value" than concat(variadic '{}'::int[]) what is correct.
Opposite behave returns empty string in both variants and It is some
when I don't feel well.
Regards
Pavel
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Pavel Stehule <pavel.stehule@gmail.com> writes:
what should be result of concat(variadic NULL::int[])
I enabled this use case, but what should be result?
I think there are two somewhat defensible theories:
(1) punt, and return NULL overall. So in this case the variadic
function would act as if it were STRICT. That seems a bit weird though
if the function is not strict otherwise.
(2) Treat the NULL as if it were a zero-length array, giving rise to
zero ordinary parameters. This could be problematic if the function
can't cope very well with zero parameters ... but on the other hand,
if it can't do so, then what will it do with VARIADIC '{}'::int[] ?
I lean a little bit towards (2) but it's definitely a judgment call.
Anybody have any other arguments one way or the other?
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2013/1/23 Tom Lane <tgl@sss.pgh.pa.us>:
Pavel Stehule <pavel.stehule@gmail.com> writes:
what should be result of concat(variadic NULL::int[])
I enabled this use case, but what should be result?I think there are two somewhat defensible theories:
(1) punt, and return NULL overall. So in this case the variadic
function would act as if it were STRICT. That seems a bit weird though
if the function is not strict otherwise.(2) Treat the NULL as if it were a zero-length array, giving rise to
zero ordinary parameters. This could be problematic if the function
can't cope very well with zero parameters ... but on the other hand,
if it can't do so, then what will it do with VARIADIC '{}'::int[] ?
This is repeated question - how much is NULL ~ '{}'
There is only one precedent, I think
postgres=# select '>>>' || array_to_string('{}'::int[], '') || '<<<';
?column?
----------
<<<
(1 row)
postgres=# select '>>>' || array_to_string(NULL::int[], '') || '<<<';
?column?
----------
(1 row)
but this function is STRICT - so there is no precedent :(
I lean a little bit towards (2) but it's definitely a judgment call.
Anybody have any other arguments one way or the other?
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2013/1/23 Pavel Stehule <pavel.stehule@gmail.com>:
2013/1/23 Tom Lane <tgl@sss.pgh.pa.us>:
Pavel Stehule <pavel.stehule@gmail.com> writes:
what should be result of concat(variadic NULL::int[])
I enabled this use case, but what should be result?I think there are two somewhat defensible theories:
(1) punt, and return NULL overall. So in this case the variadic
function would act as if it were STRICT. That seems a bit weird though
if the function is not strict otherwise.(2) Treat the NULL as if it were a zero-length array, giving rise to
zero ordinary parameters. This could be problematic if the function
can't cope very well with zero parameters ... but on the other hand,
if it can't do so, then what will it do with VARIADIC '{}'::int[] ?This is repeated question - how much is NULL ~ '{}'
There is only one precedent, I think
postgres=# select '>>>' || array_to_string('{}'::int[], '') || '<<<';
?column?
----------<<<
(1 row)
postgres=# select '>>>' || array_to_string(NULL::int[], '') || '<<<';
?column?
----------(1 row)
but this function is STRICT - so there is no precedent :(
next related example
CREATE OR REPLACE FUNCTION public.myleast(VARIADIC integer[])
RETURNS integer
LANGUAGE sql
AS $function$
select min(v) from unnest($1) g(v)
$function$
postgres=# select myleast(variadic array[]::int[]) is null;
?column?
----------
t
(1 row)
postgres=# select myleast(variadic null::int[]) is null;
?column?
----------
t
(1 row)
so it is close to Tom (2)
concat(VARIADIC NULL::int[]) and concat(VARIADIC '{}'::int[]) should
returns NULL
it is little bit strange, but probably it is most valid
Regards
Pavel
I lean a little bit towards (2) but it's definitely a judgment call.
Anybody have any other arguments one way or the other?regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jan 23, 2013 at 2:28 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Pavel Stehule <pavel.stehule@gmail.com> writes:
what should be result of concat(variadic NULL::int[])
I enabled this use case, but what should be result?I think there are two somewhat defensible theories:
(1) punt, and return NULL overall. So in this case the variadic
function would act as if it were STRICT. That seems a bit weird though
if the function is not strict otherwise.(2) Treat the NULL as if it were a zero-length array, giving rise to
zero ordinary parameters. This could be problematic if the function
can't cope very well with zero parameters ... but on the other hand,
if it can't do so, then what will it do with VARIADIC '{}'::int[] ?I lean a little bit towards (2) but it's definitely a judgment call.
Anybody have any other arguments one way or the other?
I'd like to vote for "it probably doesn't matter very much, so let's
just pick whatever makes the code simplest". :-)
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Pavel Stehule <pavel.stehule@gmail.com> writes:
next related example
CREATE OR REPLACE FUNCTION public.myleast(VARIADIC integer[])
RETURNS integer
LANGUAGE sql
AS $function$
select min(v) from unnest($1) g(v)
$function$
The reason you get a null from that is that (1) unnest() produces zero
rows out for either a null or empty-array input, and (2) min() over
zero rows produces NULL.
In a lot of cases, it's not very sane for aggregates over no rows to
produce NULL; the best-known example is that SUM() produces NULL, when
anyone who'd not suffered brain-damage from sitting on the SQL committee
would have made it return zero. So I'm not very comfortable with
generalizing from this specific case to decide that NULL is the
universally right result.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2013/1/23 Tom Lane <tgl@sss.pgh.pa.us>:
Pavel Stehule <pavel.stehule@gmail.com> writes:
next related example
CREATE OR REPLACE FUNCTION public.myleast(VARIADIC integer[])
RETURNS integer
LANGUAGE sql
AS $function$
select min(v) from unnest($1) g(v)
$function$The reason you get a null from that is that (1) unnest() produces zero
rows out for either a null or empty-array input, and (2) min() over
zero rows produces NULL.In a lot of cases, it's not very sane for aggregates over no rows to
produce NULL; the best-known example is that SUM() produces NULL, when
anyone who'd not suffered brain-damage from sitting on the SQL committee
would have made it return zero. So I'm not very comfortable with
generalizing from this specific case to decide that NULL is the
universally right result.
I am testing some situation and there are no consistent idea - can we
talk just only about functions concat and concat_ws?
these functions are really specific - now we talk about corner use
case and strongly PostgreSQL proprietary solution. So any solution
should not too bad.
Difference between all solution will by maximally +/- 4 simple rows
per any function.
Possibilities
1) A. concat(variadic NULL) => empty string, B. concat(variadic '{}')
=> empty string -- if we accept @A, then B is ok
2) A. concat(variadic NULL) => NULL, B. concat(variadic '{}') => NULL
-- question - is @B valid ?
3) A. concat(variadic NULL) => NULL, B. concat(variadic '{}) => empty string
There are no other possibility.
I can live with any variant - probably we find any precedent to any
variant in our code or in ANSI SQL.
I like @2 as general concept for PostgreSQL variadic functions, but
when we talk about concat() and concat_ws() @1 is valid too.
Please, can somebody say his opinion early
Regards
Pavel
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello
so there is updated version
+ some lines of doc
+ new regress tests
there are no reply to my previous mail - so I choose
concat(variadic null) ---> NULL
concat(variadic '{}') ---> empty string
this behave should not be precedent for any other variadic "any"
function, because concat() and concat_ws() functions has a specific
behave - when it is called with one parameter returns string or empty
string
Regards
Pavel
2013/1/23 Pavel Stehule <pavel.stehule@gmail.com>:
Show quoted text
2013/1/23 Tom Lane <tgl@sss.pgh.pa.us>:
Pavel Stehule <pavel.stehule@gmail.com> writes:
next related example
CREATE OR REPLACE FUNCTION public.myleast(VARIADIC integer[])
RETURNS integer
LANGUAGE sql
AS $function$
select min(v) from unnest($1) g(v)
$function$The reason you get a null from that is that (1) unnest() produces zero
rows out for either a null or empty-array input, and (2) min() over
zero rows produces NULL.In a lot of cases, it's not very sane for aggregates over no rows to
produce NULL; the best-known example is that SUM() produces NULL, when
anyone who'd not suffered brain-damage from sitting on the SQL committee
would have made it return zero. So I'm not very comfortable with
generalizing from this specific case to decide that NULL is the
universally right result.I am testing some situation and there are no consistent idea - can we
talk just only about functions concat and concat_ws?these functions are really specific - now we talk about corner use
case and strongly PostgreSQL proprietary solution. So any solution
should not too bad.Difference between all solution will by maximally +/- 4 simple rows
per any function.Possibilities
1) A. concat(variadic NULL) => empty string, B. concat(variadic '{}')
=> empty string -- if we accept @A, then B is ok
2) A. concat(variadic NULL) => NULL, B. concat(variadic '{}') => NULL
-- question - is @B valid ?
3) A. concat(variadic NULL) => NULL, B. concat(variadic '{}) => empty stringThere are no other possibility.
I can live with any variant - probably we find any precedent to any
variant in our code or in ANSI SQL.I like @2 as general concept for PostgreSQL variadic functions, but
when we talk about concat() and concat_ws() @1 is valid too.Please, can somebody say his opinion early
Regards
Pavel
regards, tom lane
Attachments:
variadic_any_fix_20130124.patchapplication/octet-stream; name=variadic_any_fix_20130124.patchDownload
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 2034,2039 ****
--- 2034,2049 ----
</table>
<para>
+ Functions <function>concat</function>, <function>concat_ws</function> and
+ <function>format</function> are variadic - variadic arguments should be passed
+ as usual or as array that is labeled as <literal>VARIADIC</literal>.
+ Functions <function>concat</function> and <function>concat_ws</function>
+ returns NULL, when variadic array argument is NULL and empty string
+ when variadic array argument is empty array. Result of <function>format</function>
+ primary depends on content of fmt string.
+ </para>
+
+ <para>
See also the aggregate function <function>string_agg</function> in
<xref linkend="functions-aggregate">.
</para>
*** 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);
On 01/25/2013 01:32 AM, Pavel Stehule wrote:
Hello
so there is updated version
+ some lines of doc
+ new regress teststhere are no reply to my previous mail - so I choose
concat(variadic null) ---> NULL
concat(variadic '{}') ---> empty string
I'd love to see this fix still make it into 9.3.
--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Craig Ringer <craig@2ndQuadrant.com> writes:
I'd love to see this fix still make it into 9.3.
Working on it right now, actually.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2013/1/25 Tom Lane <tgl@sss.pgh.pa.us>:
Craig Ringer <craig@2ndQuadrant.com> writes:
I'd love to see this fix still make it into 9.3.
Working on it right now, actually.
Thank you all for collaboration
Best regards
Pavel
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers