mirror of
https://github.com/postgres/postgres.git
synced 2026-10-23 13:29:09 +03:00
Preserve typmod and collation when reducing an expression to its input
eval_const_expressions() can reduce a COALESCE, CASE, or NULLIF expression to one of its inputs. The input, however, does not necessarily expose the same typmod and collation as the expression it replaces. In particular, an explicit COLLATE clause on one input determines the collation of the whole expression, while the other inputs keep their own collations. If the expression was reduced to one of those other inputs, anything that relies on exprCollation(), such as sorting or grouping on the expression, would use the wrong collation and could produce wrong results. For CASE and NULLIF this is a long-standing oversight. For COALESCE it has become much easier to hit since commits931766aaeand10c4fe074, which reduce a COALESCE with a single surviving argument, or with a leading non-nullable argument, to that argument. To fix, apply applyRelabelType() to the replacement in these cases, so that it exposes the type, typmod, and collation of the expression it replaces. Back-patch to v19 only. The CASE and NULLIF cases exist in older branches too, but they are not back-patched further, given the lack of field reports and the fact that the fix visibly changes the typmod exposed by a reduced CASE expression. Reported-by: Shihao Zhong <zhong950419@gmail.com> Author: Shihao Zhong <zhong950419@gmail.com> Author: Richard Guo <guofenglinux@gmail.com> Discussion: https://postgr.es/m/CAGRkXqR7VXacmRP41UBaM5u4aseeCH5ECVmQgn_YS=L7=xdhTw@mail.gmail.com Backpatch-through: 19
This commit is contained in:
@@ -3262,13 +3262,23 @@ eval_const_expressions_mutator(Node *node,
|
||||
/* Copy the node and const-simplify its arguments */
|
||||
expr = (NullIfExpr *) ece_generic_processing(node);
|
||||
|
||||
/* If either argument is NULL they can't be equal */
|
||||
/*
|
||||
* If either argument is NULL they can't be equal, so the
|
||||
* result is the first argument, relabeled if needed to expose
|
||||
* the NULLIF's typmod and collation
|
||||
*/
|
||||
foreach(arg, expr->args)
|
||||
{
|
||||
if (!IsA(lfirst(arg), Const))
|
||||
has_nonconst_input = true;
|
||||
else if (((Const *) lfirst(arg))->constisnull)
|
||||
return (Node *) linitial(expr->args);
|
||||
return applyRelabelType((Node *) linitial(expr->args),
|
||||
expr->opresulttype,
|
||||
exprTypmod(node),
|
||||
expr->opcollid,
|
||||
COERCE_IMPLICIT_CAST,
|
||||
-1,
|
||||
false);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -3750,10 +3760,17 @@ eval_const_expressions_mutator(Node *node,
|
||||
|
||||
/*
|
||||
* If no non-FALSE alternatives, CASE reduces to the default
|
||||
* result
|
||||
* result, relabeled if needed to expose the CASE's typmod and
|
||||
* collation
|
||||
*/
|
||||
if (newargs == NIL)
|
||||
return defresult;
|
||||
return applyRelabelType(defresult,
|
||||
caseexpr->casetype,
|
||||
exprTypmod(node),
|
||||
caseexpr->casecollid,
|
||||
COERCE_IMPLICIT_CAST,
|
||||
-1,
|
||||
false);
|
||||
/* Otherwise we need a new CASE node */
|
||||
newcase = makeNode(CaseExpr);
|
||||
newcase->casetype = caseexpr->casetype;
|
||||
@@ -3820,26 +3837,19 @@ eval_const_expressions_mutator(Node *node,
|
||||
|
||||
/*
|
||||
* We can remove null constants from the list. For a
|
||||
* nonnullable expression, if it has not been preceded by
|
||||
* any non-null-constant expressions then it is the
|
||||
* result. Otherwise, it's the next argument, but we can
|
||||
* drop following arguments since they will never be
|
||||
* reached.
|
||||
* nonnullable expression, we can drop following arguments
|
||||
* since they will never be reached.
|
||||
*/
|
||||
if (IsA(e, Const))
|
||||
{
|
||||
if (((Const *) e)->constisnull)
|
||||
continue; /* drop null constant */
|
||||
if (newargs == NIL)
|
||||
return e; /* first expr */
|
||||
newargs = lappend(newargs, e);
|
||||
break;
|
||||
}
|
||||
if (expr_is_nonnullable(context->root, (Expr *) e,
|
||||
NOTNULL_SOURCE_HASHTABLE))
|
||||
{
|
||||
if (newargs == NIL)
|
||||
return e; /* first expr */
|
||||
newargs = lappend(newargs, e);
|
||||
break;
|
||||
}
|
||||
@@ -3858,10 +3868,18 @@ eval_const_expressions_mutator(Node *node,
|
||||
|
||||
/*
|
||||
* If there's exactly one surviving argument, we no longer
|
||||
* need COALESCE at all: the result is that argument
|
||||
* need COALESCE at all: the result is that argument,
|
||||
* relabeled if needed to expose the COALESCE's typmod and
|
||||
* collation
|
||||
*/
|
||||
if (list_length(newargs) == 1)
|
||||
return (Node *) linitial(newargs);
|
||||
return applyRelabelType((Node *) linitial(newargs),
|
||||
coalesceexpr->coalescetype,
|
||||
exprTypmod(node),
|
||||
coalesceexpr->coalescecollid,
|
||||
COERCE_IMPLICIT_CAST,
|
||||
-1,
|
||||
false);
|
||||
|
||||
newcoalesce = makeNode(CoalesceExpr);
|
||||
newcoalesce->coalescetype = coalesceexpr->coalescetype;
|
||||
|
||||
@@ -3860,6 +3860,31 @@ SELECT a, b FROM groupby_collation_t GROUP BY a, b ORDER BY a, b;
|
||||
(2 rows)
|
||||
|
||||
DROP TABLE groupby_collation_t;
|
||||
--
|
||||
-- An expression that is simplified to one of its inputs must keep its own
|
||||
-- collation.
|
||||
--
|
||||
CREATE TABLE simplify_collation_t (a text COLLATE "C" NOT NULL, b text);
|
||||
INSERT INTO simplify_collation_t VALUES ('foo'), ('FOO');
|
||||
SELECT count(*) FROM (SELECT COALESCE(a, b COLLATE case_insensitive) FROM simplify_collation_t GROUP BY 1);
|
||||
count
|
||||
-------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT count(*) FROM (SELECT CASE WHEN true THEN a ELSE b COLLATE case_insensitive END FROM simplify_collation_t GROUP BY 1);
|
||||
count
|
||||
-------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT count(*) FROM (SELECT NULLIF(a, NULL::text COLLATE case_insensitive) FROM simplify_collation_t GROUP BY 1);
|
||||
count
|
||||
-------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
DROP TABLE simplify_collation_t;
|
||||
-- virtual generated columns
|
||||
CREATE TABLE t5 (
|
||||
a int,
|
||||
|
||||
@@ -1492,6 +1492,19 @@ SELECT a, b FROM groupby_collation_t GROUP BY a, b ORDER BY a, b;
|
||||
|
||||
DROP TABLE groupby_collation_t;
|
||||
|
||||
--
|
||||
-- An expression that is simplified to one of its inputs must keep its own
|
||||
-- collation.
|
||||
--
|
||||
CREATE TABLE simplify_collation_t (a text COLLATE "C" NOT NULL, b text);
|
||||
INSERT INTO simplify_collation_t VALUES ('foo'), ('FOO');
|
||||
|
||||
SELECT count(*) FROM (SELECT COALESCE(a, b COLLATE case_insensitive) FROM simplify_collation_t GROUP BY 1);
|
||||
SELECT count(*) FROM (SELECT CASE WHEN true THEN a ELSE b COLLATE case_insensitive END FROM simplify_collation_t GROUP BY 1);
|
||||
SELECT count(*) FROM (SELECT NULLIF(a, NULL::text COLLATE case_insensitive) FROM simplify_collation_t GROUP BY 1);
|
||||
|
||||
DROP TABLE simplify_collation_t;
|
||||
|
||||
-- virtual generated columns
|
||||
CREATE TABLE t5 (
|
||||
a int,
|
||||
|
||||
Reference in New Issue
Block a user