Hi
čt 21. 2. 2019 v 3:20 odesílatel Chapman Flack <[email protected]>
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: not tested
>
> Latest patch passes installcheck-world as of d26a810 and does what it sets
> out to do.
>
> I am not sure I have an answer to the objections being raised on grounds
> of taste. To me, it's persuasive that GREATEST and LEAST are described in
> the docco as functions, they are used much like variadic functions, and
> this patch allows them to be used in the ways you would expect variadic
> functions to be usable.
>
> But as to technical readiness, this builds and passes installcheck-world.
> The functions behave as expected (and return null if passed an empty array,
> or an array containing only nulls, or variadic null::foo[]).
>
> Still no corresponding regression tests or doc.
>
> The new status of this patch is: Waiting on Author
>
I wrote doc (just one sentence) and minimal test. Both can be enhanced.
Regards
Pavel
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/execExpr.c b/src/backend/executor/execExpr.c
index db3777d15e..01d7f0e02c 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -1854,13 +1854,18 @@ ExecInitExprRec(Expr *node, ExprState *state,
case T_MinMaxExpr:
{
MinMaxExpr *minmaxexpr = (MinMaxExpr *) node;
- int nelems = list_length(minmaxexpr->args);
+ int nelems;
TypeCacheEntry *typentry;
FmgrInfo *finfo;
FunctionCallInfo fcinfo;
ListCell *lc;
int off;
+ if (minmaxexpr->args)
+ nelems = list_length(minmaxexpr->args);
+ else
+ nelems = 1;
+
/* Look up the btree comparison function for the datatype */
typentry = lookup_type_cache(minmaxexpr->minmaxtype,
TYPECACHE_CMP_PROC);
@@ -1897,16 +1902,29 @@ ExecInitExprRec(Expr *node, ExprState *state,
scratch.d.minmax.finfo = finfo;
scratch.d.minmax.fcinfo_data = fcinfo;
- /* evaluate expressions into minmax->values/nulls */
- off = 0;
- foreach(lc, minmaxexpr->args)
+ if (minmaxexpr->args)
{
- Expr *e = (Expr *) lfirst(lc);
+ scratch.d.minmax.variadic_arg = false;
- ExecInitExprRec(e, state,
- &scratch.d.minmax.values[off],
- &scratch.d.minmax.nulls[off]);
- off++;
+ /* evaluate expressions into minmax->values/nulls */
+ off = 0;
+ foreach(lc, minmaxexpr->args)
+ {
+ Expr *e = (Expr *) lfirst(lc);
+
+ ExecInitExprRec(e, state,
+ &scratch.d.minmax.values[off],
+ &scratch.d.minmax.nulls[off]);
+ off++;
+ }
+ }
+ else
+ {
+ scratch.d.minmax.variadic_arg = true;
+
+ ExecInitExprRec(minmaxexpr->variadic_arg, state,
+ &scratch.d.minmax.values[0],
+ &scratch.d.minmax.nulls[0]);
}
/* and push the final comparison */
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index a018925d4e..748c950885 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2819,6 +2819,55 @@ ExecEvalMinMax(ExprState *state, ExprEvalStep *op)
/* default to null result */
*op->resnull = true;
+ if (op->d.minmax.variadic_arg)
+ {
+ ArrayIterator array_iterator;
+ ArrayType *arr;
+ Datum value;
+ bool isnull;
+
+ if (nulls[0])
+ return;
+
+ arr = DatumGetArrayTypeP(values[0]);
+
+ array_iterator = array_create_iterator(arr, 0, NULL);
+ while (array_iterate(array_iterator, &value, &isnull))
+ {
+ if (isnull)
+ continue;
+
+ if (*op->resnull)
+ {
+ /* first nonnull input */
+ *op->resvalue = value;
+ *op->resnull = false;
+ }
+ else
+ {
+ int cmpresult;
+
+ Assert(fcinfo->nargs == 2);
+
+ /* apply comparison function */
+ fcinfo->args[0].value = *op->resvalue;
+ 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 = value;
+ else if (cmpresult < 0 && operator == IS_GREATEST)
+ *op->resvalue = value;
+ }
+ }
+
+ return;
+ }
+
for (off = 0; off < op->d.minmax.nelems; off++)
{
/* ignore NULL inputs */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index e15724bb0e..e8ad89799f 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1942,6 +1942,7 @@ _copyMinMaxExpr(const MinMaxExpr *from)
COPY_SCALAR_FIELD(inputcollid);
COPY_SCALAR_FIELD(op);
COPY_NODE_FIELD(args);
+ COPY_NODE_FIELD(variadic_arg);
COPY_LOCATION_FIELD(location);
return newnode;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 31499eb798..6623c7800d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -635,6 +635,7 @@ _equalMinMaxExpr(const MinMaxExpr *a, const MinMaxExpr *b)
COMPARE_SCALAR_FIELD(inputcollid);
COMPARE_SCALAR_FIELD(op);
COMPARE_NODE_FIELD(args);
+ COMPARE_NODE_FIELD(variadic_arg);
COMPARE_LOCATION_FIELD(location);
return true;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 8ed30c011a..52fcf8d9b0 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -465,6 +465,9 @@ exprTypmod(const Node *expr)
int32 typmod;
ListCell *arg;
+ if (mexpr->variadic_arg)
+ return exprTypmod((Node *) mexpr->variadic_arg);
+
if (exprType((Node *) linitial(mexpr->args)) != minmaxtype)
return -1;
typmod = exprTypmod((Node *) linitial(mexpr->args));
@@ -2072,7 +2075,15 @@ expression_tree_walker(Node *node,
case T_CoalesceExpr:
return walker(((CoalesceExpr *) node)->args, context);
case T_MinMaxExpr:
- return walker(((MinMaxExpr *) node)->args, context);
+ {
+ MinMaxExpr *mexpr = (MinMaxExpr *) node;
+
+ if (walker(mexpr->args, context))
+ return true;
+ if (walker(mexpr->variadic_arg, context))
+ return true;
+ }
+ break;
case T_XmlExpr:
{
XmlExpr *xexpr = (XmlExpr *) node;
@@ -2839,6 +2850,7 @@ expression_tree_mutator(Node *node,
FLATCOPY(newnode, minmaxexpr, MinMaxExpr);
MUTATE(newnode->args, minmaxexpr->args, List *);
+ MUTATE(newnode->variadic_arg, minmaxexpr->variadic_arg, Expr *);
return (Node *) newnode;
}
break;
@@ -3369,7 +3381,15 @@ raw_expression_tree_walker(Node *node,
case T_CoalesceExpr:
return walker(((CoalesceExpr *) node)->args, context);
case T_MinMaxExpr:
- return walker(((MinMaxExpr *) node)->args, context);
+ {
+ MinMaxExpr *mexpr = (MinMaxExpr *) node;
+
+ if (walker(mexpr->args, context))
+ return true;
+ if (walker(mexpr->variadic_arg, context))
+ return true;
+ }
+ break;
case T_XmlExpr:
{
XmlExpr *xexpr = (XmlExpr *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 65302fe65b..531aa07b61 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1498,6 +1498,7 @@ _outMinMaxExpr(StringInfo str, const MinMaxExpr *node)
WRITE_OID_FIELD(inputcollid);
WRITE_ENUM_FIELD(op, MinMaxOp);
WRITE_NODE_FIELD(args);
+ WRITE_NODE_FIELD(variadic_arg);
WRITE_LOCATION_FIELD(location);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 5aa42242a9..14641ddf51 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1088,6 +1088,7 @@ _readMinMaxExpr(void)
READ_OID_FIELD(inputcollid);
READ_ENUM_FIELD(op, MinMaxOp);
READ_NODE_FIELD(args);
+ READ_NODE_FIELD(variadic_arg);
READ_LOCATION_FIELD(location);
READ_DONE();
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a68f78e0e0..975eeb60ff 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->variadic_arg = (Expr *) $4;
+ v->op = IS_GREATEST;
+ v->location = @1;
+ $$ = (Node *)v;
+ }
+ | LEAST '(' VARIADIC a_expr ')'
+ {
+ MinMaxExpr *v = makeNode(MinMaxExpr);
+ v->variadic_arg = (Expr *) $4;
+ v->op = IS_LEAST;
+ 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..711de0b5b5 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2263,35 +2263,60 @@ transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m)
MinMaxExpr *newm = makeNode(MinMaxExpr);
List *newargs = NIL;
List *newcoercedargs = NIL;
- const char *funcname = (m->op == IS_GREATEST) ? "GREATEST" : "LEAST";
- ListCell *args;
newm->op = m->op;
- foreach(args, m->args)
+
+ if (m->args)
{
- Node *e = (Node *) lfirst(args);
- Node *newe;
+ ListCell *args;
+ const char *funcname = (m->op == IS_GREATEST) ? "GREATEST" : "LEAST";
- newe = transformExprRecurse(pstate, e);
- newargs = lappend(newargs, newe);
- }
+ foreach(args, m->args)
+ {
+ 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 = transformExprRecurse(pstate, e);
+ newargs = lappend(newargs, newe);
+ }
- /* Convert arguments if necessary */
- foreach(args, newargs)
+ newm->minmaxtype = select_common_type(pstate, newargs, funcname, NULL);
+ /* minmaxcollid and inputcollid will be set by parse_collate.c */
+
+ /* 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;
+ }
+ else
{
- Node *e = (Node *) lfirst(args);
- Node *newe;
+ Oid input_type;
+ Oid element_typeid;
- newe = coerce_to_common_type(pstate, e,
- newm->minmaxtype,
- funcname);
- newcoercedargs = lappend(newcoercedargs, newe);
+ Assert(m->variadic_arg);
+
+ newm->variadic_arg = (Expr *) transformExprRecurse(pstate, (Node *) m->variadic_arg);
+ input_type = exprType((Node *) newm->variadic_arg);
+
+ if (input_type == InvalidOid)
+ elog(ERROR, "cannot to determinate result type");
+
+ element_typeid = get_base_element_type(input_type);
+ if (!OidIsValid(element_typeid))
+ elog(ERROR, "expected a array parameter");
+
+ newm->minmaxtype = element_typeid;
}
- newm->args = newcoercedargs;
newm->location = m->location;
return (Node *) newm;
}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 1258092dc8..3431b39d53 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8474,7 +8474,17 @@ get_rule_expr(Node *node, deparse_context *context,
appendStringInfoString(buf, "LEAST(");
break;
}
- get_rule_expr((Node *) minmaxexpr->args, context, true);
+
+ if (minmaxexpr->args)
+ {
+ get_rule_expr((Node *) minmaxexpr->args, context, true);
+ }
+ else
+ {
+ appendStringInfoString(buf, "VARIADIC ");
+ get_rule_expr((Node *) minmaxexpr->variadic_arg, context, true);
+ }
+
appendStringInfoChar(buf, ')');
}
break;
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 7aacdc5d04..7b74f0d9f3 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -459,6 +459,7 @@ typedef struct ExprEvalStep
/* workspace for argument values */
Datum *values;
bool *nulls;
+ bool variadic_arg;
int nelems;
/* is it GREATEST or LEAST? */
MinMaxOp op;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index a7efae7038..4a5a2a4b5a 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1091,6 +1091,7 @@ typedef struct MinMaxExpr
Oid inputcollid; /* OID of collation that function should use */
MinMaxOp op; /* function to execute */
List *args; /* the arguments */
+ Expr *variadic_arg; /* the variadic argument */
int location; /* token location, or -1 if unknown */
} MinMaxExpr;
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..f79c4d4efc 100644
--- a/src/test/regress/sql/case.sql
+++ b/src/test/regress/sql/case.sql
@@ -252,3 +252,18 @@ 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]);
+
+