so 23. 2. 2019 v 4:50 odesÃlatel Chapman Flack <c...@anastigmatix.net> napsal:
> The following review has been posted through the commitfest application: > make installcheck-world: tested, passed > Implements feature: tested, passed > Spec compliant: not tested > Documentation: tested, passed > > The latest patch provides the same functionality without growing the size > of struct ExprEvalStep, and without using the presence/absence of > args/variadic_args to distinguish the cases. It now uses the args field > consistently, and distinguishes the cases with new op constants, > IS_GREATEST_VARIADIC and IS_LEAST_VARIADIC, assigned at parse time. I > concede Tom's points about the comparative wartiness of the former patch. > > I'll change to WoA, though, for a few loose ends: > > In transformMinMaxExpr: > The assignment of funcname doesn't look right. > Two new errors are elogs. If they can be caused by user input (I'm sure > the second one can), should they not be ereports? > In fact, I think the second one should copy the equivalent one from > parse_func.c: > > > ereport(ERROR, > > (errcode(ERRCODE_DATATYPE_MISMATCH), > > errmsg("VARIADIC argument must be an array"), > > parser_errposition(pstate, > > exprLocation((Node *) llast(fargs))))); > > ... both for consistency of the message, and so (I assume) it can use the > existing translations for that message string. > good idea, done > I am not sure if there is a way for user input to trigger the first one. > Perhaps it can stay an elog if not. In any case, s/to > determinate/determine/. > It is +/- internal error and usually should not be touched - so there I prefer elog. I fix message > > In EvalExecMinMax: > > + if (cmpresult > 0 && > + (operator == IS_LEAST || operator == > IS_LEAST_VARIADIC)) > + *op->resvalue = value; > + else if (cmpresult < 0 && > + (operator == IS_GREATEST || > operator == IS_GREATEST_VARIADIC)) > > would it make sense to just compute a boolean isleast before entering the > loop, to get simply (cmpresult > 0 && isleast) or (cmpresult < 0 && > !isleast) inside the loop? I'm unsure whether to assume the compiler will > see that opportunity. > I am have not strong opinion about it. Personally I dislike a two variables - but any discussion is partially about premature optimization. What about using macros? > Regards, > -Chap > > The new status of this patch is: Waiting on Author >
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 86ff4e5c9e..aa4e80cca7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -12679,6 +12679,12 @@ SELECT NULLIF(value, '(none)') ... </synopsis> <synopsis> <function>LEAST</function>(<replaceable>value</replaceable> <optional>, ...</optional>) +</synopsis> +<synopsis> +<function>GREATEST</function>(VARIADIC <replaceable>anyarray</replaceable>) +</synopsis> +<synopsis> +<function>LEAST</function>(VARIADIC <replaceable>anyarray</replaceable>) </synopsis> <para> @@ -12697,6 +12703,11 @@ SELECT NULLIF(value, '(none)') ... make them return NULL if any argument is NULL, rather than only when all are NULL. </para> + + <para> + These functions supports passing parameters as a array after + <literal>VARIADIC</literal> keyword. + </para> </sect2> </sect1> diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index a018925d4e..8ac4628ec1 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -2811,6 +2811,8 @@ ExecEvalMinMax(ExprState *state, ExprEvalStep *op) FunctionCallInfo fcinfo = op->d.minmax.fcinfo_data; MinMaxOp operator = op->d.minmax.op; int off; + ArrayIterator array_iterator = NULL; + int nelems; /* set at initialization */ Assert(fcinfo->args[0].isnull == false); @@ -2819,16 +2821,49 @@ ExecEvalMinMax(ExprState *state, ExprEvalStep *op) /* default to null result */ *op->resnull = true; - for (off = 0; off < op->d.minmax.nelems; off++) + if (IsMinMaxVariadic(operator)) { + ArrayType *arr; + + /* result is null, when variadic argument is NULL */ + if (nulls[0]) + return; + + /* prepare iterarator */ + arr = DatumGetArrayTypeP(values[0]); + array_iterator = array_create_iterator(arr, 0, NULL); + + nelems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr)); + } + else + nelems = op->d.minmax.nelems; + + for (off = 0; off < nelems; off++) + { + Datum value; + bool isnull; + + if (array_iterator) + { + bool has_data PG_USED_FOR_ASSERTS_ONLY; + + has_data = array_iterate(array_iterator, &value, &isnull); + Assert(has_data); + } + else + { + value = values[off]; + isnull = nulls[off]; + } + /* ignore NULL inputs */ - if (nulls[off]) + if (isnull) continue; if (*op->resnull) { /* first nonnull input, adopt value */ - *op->resvalue = values[off]; + *op->resvalue = value; *op->resnull = false; } else @@ -2837,19 +2872,23 @@ ExecEvalMinMax(ExprState *state, ExprEvalStep *op) /* apply comparison function */ fcinfo->args[0].value = *op->resvalue; - fcinfo->args[1].value = values[off]; + fcinfo->args[1].value = value; fcinfo->isnull = false; cmpresult = DatumGetInt32(FunctionCallInvoke(fcinfo)); if (fcinfo->isnull) /* probably should not happen */ continue; - if (cmpresult > 0 && operator == IS_LEAST) - *op->resvalue = values[off]; - else if (cmpresult < 0 && operator == IS_GREATEST) - *op->resvalue = values[off]; + if (cmpresult > 0 && IsMinMaxLeast(operator)) + *op->resvalue = value; + else if (cmpresult < 0 && IsMinMaxGreatest(operator)) + *op->resvalue = value; } } + + /* release iterator */ + if (array_iterator) + array_free_iterator(array_iterator); } /* diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 8ed30c011a..f68cebfee8 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -465,7 +465,8 @@ exprTypmod(const Node *expr) int32 typmod; ListCell *arg; - if (exprType((Node *) linitial(mexpr->args)) != minmaxtype) + if (!IsMinMaxVariadic(mexpr->op) && + exprType((Node *) linitial(mexpr->args)) != minmaxtype) return -1; typmod = exprTypmod((Node *) linitial(mexpr->args)); if (typmod < 0) diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index a68f78e0e0..65f44a8df6 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -13803,6 +13803,22 @@ func_expr_common_subexpr: v->location = @1; $$ = (Node *)v; } + | GREATEST '(' VARIADIC a_expr ')' + { + MinMaxExpr *v = makeNode(MinMaxExpr); + v->args = list_make1($4); + v->op = IS_GREATEST_VARIADIC; + v->location = @1; + $$ = (Node *)v; + } + | LEAST '(' VARIADIC a_expr ')' + { + MinMaxExpr *v = makeNode(MinMaxExpr); + v->args = list_make1($4); + v->op = IS_LEAST_VARIADIC; + v->location = @1; + $$ = (Node *)v; + } | XMLCONCAT '(' expr_list ')' { $$ = makeXmlExpr(IS_XMLCONCAT, NULL, NIL, $3, @1); diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index e559353529..45aae3f89b 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -2276,22 +2276,47 @@ transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m) newargs = lappend(newargs, newe); } - newm->minmaxtype = select_common_type(pstate, newargs, funcname, NULL); - /* minmaxcollid and inputcollid will be set by parse_collate.c */ + if (IsMinMaxVariadic(newm->op)) + { + Oid array_typid; + Oid element_typid; - /* Convert arguments if necessary */ - foreach(args, newargs) + array_typid = exprType(linitial(newargs)); + + if (array_typid == InvalidOid) + elog(ERROR, "cannot to determine result type"); + + element_typid = get_base_element_type(array_typid); + if (!OidIsValid(element_typid)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("VARIADIC argument must be an array"), + parser_errposition(pstate, + exprLocation((Node *) linitial(newargs))))); + + newm->minmaxtype = element_typid; + newm->args = newargs; + } + else { - Node *e = (Node *) lfirst(args); - Node *newe; + newm->minmaxtype = select_common_type(pstate, newargs, funcname, NULL); + /* minmaxcollid and inputcollid will be set by parse_collate.c */ - newe = coerce_to_common_type(pstate, e, - newm->minmaxtype, - funcname); - newcoercedargs = lappend(newcoercedargs, newe); + /* Convert arguments if necessary */ + foreach(args, newargs) + { + Node *e = (Node *) lfirst(args); + Node *newe; + + newe = coerce_to_common_type(pstate, e, + newm->minmaxtype, + funcname); + newcoercedargs = lappend(newcoercedargs, newe); + } + + newm->args = newcoercedargs; } - newm->args = newcoercedargs; newm->location = m->location; return (Node *) newm; } diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 0e9598ebfe..de1794201a 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1839,15 +1839,20 @@ FigureColnameInternal(Node *node, char **name) *name = "coalesce"; return 2; case T_MinMaxExpr: - /* make greatest/least act like a regular function */ - switch (((MinMaxExpr *) node)->op) { - case IS_GREATEST: + /* make greatest/least act like a regular function */ + MinMaxOp op = ((MinMaxExpr *) node)->op; + + if (IsMinMaxGreatest(op)) + { *name = "greatest"; return 2; - case IS_LEAST: + } + else if (IsMinMaxLeast(op)) + { *name = "least"; return 2; + } } break; case T_SQLValueFunction: diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 1258092dc8..b5a58403ee 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -8465,16 +8465,19 @@ get_rule_expr(Node *node, deparse_context *context, { MinMaxExpr *minmaxexpr = (MinMaxExpr *) node; - switch (minmaxexpr->op) + if (IsMinMaxGreatest(minmaxexpr->op)) + appendStringInfoString(buf, "GREATEST("); + else if (IsMinMaxLeast(minmaxexpr->op)) + appendStringInfoString(buf, "LEAST("); + + if (IsMinMaxVariadic(minmaxexpr->op)) { - case IS_GREATEST: - appendStringInfoString(buf, "GREATEST("); - break; - case IS_LEAST: - appendStringInfoString(buf, "LEAST("); - break; + appendStringInfoString(buf, "VARIADIC "); + get_rule_expr(linitial(minmaxexpr->args), context, true); } - get_rule_expr((Node *) minmaxexpr->args, context, true); + else + get_rule_expr((Node *) minmaxexpr->args, context, true); + appendStringInfoChar(buf, ')'); } break; diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index a7efae7038..d3c10f5dbd 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1080,9 +1080,18 @@ typedef struct CoalesceExpr typedef enum MinMaxOp { IS_GREATEST, - IS_LEAST + IS_LEAST, + IS_GREATEST_VARIADIC, + IS_LEAST_VARIADIC } MinMaxOp; +#define IsMinMaxVariadic(op) (op == IS_GREATEST_VARIADIC || \ + op == IS_LEAST_VARIADIC) +#define IsMinMaxLeast(op) (op == IS_LEAST || \ + op == IS_LEAST_VARIADIC) +#define IsMinMaxGreatest(op) (op == IS_GREATEST || \ + op == IS_GREATEST_VARIADIC) + typedef struct MinMaxExpr { Expr xpr; diff --git a/src/test/regress/expected/case.out b/src/test/regress/expected/case.out index c0c8acf035..eca127ed2d 100644 --- a/src/test/regress/expected/case.out +++ b/src/test/regress/expected/case.out @@ -392,3 +392,54 @@ ROLLBACK; -- DROP TABLE CASE_TBL; DROP TABLE CASE2_TBL; +-- +-- greatest and least tests +-- +SELECT least(10,20,30,40); + least +------- + 10 +(1 row) + +SELECT greatest(10,20,30,40); + greatest +---------- + 40 +(1 row) + +SELECT least(VARIADIC ARRAY[10,20,30,40]); + least +------- + 10 +(1 row) + +SELECT greatest(VARIADIC ARRAY[10,20,30,40]); + greatest +---------- + 40 +(1 row) + +SELECT least(10,20,30,40, NULL); + least +------- + 10 +(1 row) + +SELECT greatest(10,20,30,40, NULL); + greatest +---------- + 40 +(1 row) + +SELECT least(VARIADIC ARRAY[10,20,30,40, NULL]); + least +------- + 10 +(1 row) + +SELECT greatest(VARIADIC ARRAY[10,20,30,40, NULL]); + greatest +---------- + 40 +(1 row) + diff --git a/src/test/regress/sql/case.sql b/src/test/regress/sql/case.sql index 17436c524a..c482676e17 100644 --- a/src/test/regress/sql/case.sql +++ b/src/test/regress/sql/case.sql @@ -252,3 +252,16 @@ ROLLBACK; DROP TABLE CASE_TBL; DROP TABLE CASE2_TBL; + +-- +-- greatest and least tests +-- +SELECT least(10,20,30,40); +SELECT greatest(10,20,30,40); +SELECT least(VARIADIC ARRAY[10,20,30,40]); +SELECT greatest(VARIADIC ARRAY[10,20,30,40]); + +SELECT least(10,20,30,40, NULL); +SELECT greatest(10,20,30,40, NULL); +SELECT least(VARIADIC ARRAY[10,20,30,40, NULL]); +SELECT greatest(VARIADIC ARRAY[10,20,30,40, NULL]);