Commit 14a158f9 authored by Tom Lane's avatar Tom Lane

Fix interaction of CASE and ArrayCoerceExpr.

An array-type coercion appearing within a CASE that has a constant
(after const-folding) test expression was mangled by the planner, causing
all the elements of the resulting array to be equal to the coerced value
of the CASE's test expression.  This is my oversight in commit c12d570f:
that changed ArrayCoerceExpr to use a subexpression involving a
CaseTestExpr, and I didn't notice that eval_const_expressions needed an
adjustment to keep from folding such a CaseTestExpr to a constant when
it's inside a suitable CASE.

This is another in what's getting to be a depressingly long line of bugs
associated with misidentification of the referent of a CaseTestExpr.
We're overdue to redesign that mechanism; but any such fix is unlikely
to be back-patchable into v11.  As a stopgap, fix eval_const_expressions
to do what it must here.  Also add a bunch of comments pointing out the
restrictions and assumptions that are needed to make this work at all.

Also fix a related oversight: contain_context_dependent_node() was not
aware of the relationship of ArrayCoerceExpr to CaseTestExpr.  That was
somewhat fail-soft, in that the outcome of a wrong answer would be to
prevent optimizations that could have been made, but let's fix it while
we're at it.

Per bug #15471 from Matt Williams.  Back-patch to v11 where the faulty
logic came in.

Discussion: https://postgr.es/m/15471-1117f49271989bad@postgresql.org
parent c2c7c263
...@@ -1516,10 +1516,11 @@ ExecInitExprRec(Expr *node, ExprState *state, ...@@ -1516,10 +1516,11 @@ ExecInitExprRec(Expr *node, ExprState *state,
/* /*
* Read from location identified by innermost_caseval. Note * Read from location identified by innermost_caseval. Note
* that innermost_caseval could be NULL, if this node isn't * that innermost_caseval could be NULL, if this node isn't
* actually within a CASE structure; some parts of the system * actually within a CaseExpr, ArrayCoerceExpr, etc structure.
* abuse CaseTestExpr to cause a read of a value externally * That can happen because some parts of the system abuse
* supplied in econtext->caseValue_datum. We'll take care of * CaseTestExpr to cause a read of a value externally supplied
* that scenario at runtime. * in econtext->caseValue_datum. We'll take care of that
* scenario at runtime.
*/ */
scratch.opcode = EEOP_CASE_TESTVAL; scratch.opcode = EEOP_CASE_TESTVAL;
scratch.d.casetest.value = state->innermost_caseval; scratch.d.casetest.value = state->innermost_caseval;
......
...@@ -1452,7 +1452,8 @@ contain_nonstrict_functions_walker(Node *node, void *context) ...@@ -1452,7 +1452,8 @@ contain_nonstrict_functions_walker(Node *node, void *context)
* CaseTestExpr nodes must appear directly within the corresponding CaseExpr, * CaseTestExpr nodes must appear directly within the corresponding CaseExpr,
* not nested within another one, or they'll see the wrong test value. If one * not nested within another one, or they'll see the wrong test value. If one
* appears "bare" in the arguments of a SQL function, then we can't inline the * appears "bare" in the arguments of a SQL function, then we can't inline the
* SQL function for fear of creating such a situation. * SQL function for fear of creating such a situation. The same applies for
* CaseTestExpr used within the elemexpr of an ArrayCoerceExpr.
* *
* CoerceToDomainValue would have the same issue if domain CHECK expressions * CoerceToDomainValue would have the same issue if domain CHECK expressions
* could get inlined into larger expressions, but presently that's impossible. * could get inlined into larger expressions, but presently that's impossible.
...@@ -1468,7 +1469,7 @@ contain_context_dependent_node(Node *clause) ...@@ -1468,7 +1469,7 @@ contain_context_dependent_node(Node *clause)
return contain_context_dependent_node_walker(clause, &flags); return contain_context_dependent_node_walker(clause, &flags);
} }
#define CCDN_IN_CASEEXPR 0x0001 /* CaseTestExpr okay here? */ #define CCDN_CASETESTEXPR_OK 0x0001 /* CaseTestExpr okay here? */
static bool static bool
contain_context_dependent_node_walker(Node *node, int *flags) contain_context_dependent_node_walker(Node *node, int *flags)
...@@ -1476,8 +1477,8 @@ contain_context_dependent_node_walker(Node *node, int *flags) ...@@ -1476,8 +1477,8 @@ contain_context_dependent_node_walker(Node *node, int *flags)
if (node == NULL) if (node == NULL)
return false; return false;
if (IsA(node, CaseTestExpr)) if (IsA(node, CaseTestExpr))
return !(*flags & CCDN_IN_CASEEXPR); return !(*flags & CCDN_CASETESTEXPR_OK);
if (IsA(node, CaseExpr)) else if (IsA(node, CaseExpr))
{ {
CaseExpr *caseexpr = (CaseExpr *) node; CaseExpr *caseexpr = (CaseExpr *) node;
...@@ -1499,7 +1500,7 @@ contain_context_dependent_node_walker(Node *node, int *flags) ...@@ -1499,7 +1500,7 @@ contain_context_dependent_node_walker(Node *node, int *flags)
* seem worth any extra code. If there are any bare CaseTestExprs * seem worth any extra code. If there are any bare CaseTestExprs
* elsewhere in the CASE, something's wrong already. * elsewhere in the CASE, something's wrong already.
*/ */
*flags |= CCDN_IN_CASEEXPR; *flags |= CCDN_CASETESTEXPR_OK;
res = expression_tree_walker(node, res = expression_tree_walker(node,
contain_context_dependent_node_walker, contain_context_dependent_node_walker,
(void *) flags); (void *) flags);
...@@ -1507,6 +1508,24 @@ contain_context_dependent_node_walker(Node *node, int *flags) ...@@ -1507,6 +1508,24 @@ contain_context_dependent_node_walker(Node *node, int *flags)
return res; return res;
} }
} }
else if (IsA(node, ArrayCoerceExpr))
{
ArrayCoerceExpr *ac = (ArrayCoerceExpr *) node;
int save_flags;
bool res;
/* Check the array expression */
if (contain_context_dependent_node_walker((Node *) ac->arg, flags))
return true;
/* Check the elemexpr, which is allowed to contain CaseTestExpr */
save_flags = *flags;
*flags |= CCDN_CASETESTEXPR_OK;
res = contain_context_dependent_node_walker((Node *) ac->elemexpr,
flags);
*flags = save_flags;
return res;
}
return expression_tree_walker(node, contain_context_dependent_node_walker, return expression_tree_walker(node, contain_context_dependent_node_walker,
(void *) flags); (void *) flags);
} }
...@@ -3125,10 +3144,31 @@ eval_const_expressions_mutator(Node *node, ...@@ -3125,10 +3144,31 @@ eval_const_expressions_mutator(Node *node,
} }
case T_ArrayCoerceExpr: case T_ArrayCoerceExpr:
{ {
ArrayCoerceExpr *ac; ArrayCoerceExpr *ac = makeNode(ArrayCoerceExpr);
Node *save_case_val;
/* Copy the node and const-simplify its arguments */ /*
ac = (ArrayCoerceExpr *) ece_generic_processing(node); * Copy the node and const-simplify its arguments. We can't
* use ece_generic_processing() here because we need to mess
* with case_val only while processing the elemexpr.
*/
memcpy(ac, node, sizeof(ArrayCoerceExpr));
ac->arg = (Expr *)
eval_const_expressions_mutator((Node *) ac->arg,
context);
/*
* Set up for the CaseTestExpr node contained in the elemexpr.
* We must prevent it from absorbing any outer CASE value.
*/
save_case_val = context->case_val;
context->case_val = NULL;
ac->elemexpr = (Expr *)
eval_const_expressions_mutator((Node *) ac->elemexpr,
context);
context->case_val = save_case_val;
/* /*
* If constant argument and the per-element expression is * If constant argument and the per-element expression is
...@@ -3142,6 +3182,7 @@ eval_const_expressions_mutator(Node *node, ...@@ -3142,6 +3182,7 @@ eval_const_expressions_mutator(Node *node,
ac->elemexpr && !IsA(ac->elemexpr, CoerceToDomain) && ac->elemexpr && !IsA(ac->elemexpr, CoerceToDomain) &&
!contain_mutable_functions((Node *) ac->elemexpr)) !contain_mutable_functions((Node *) ac->elemexpr))
return ece_evaluate_expr(ac); return ece_evaluate_expr(ac);
return (Node *) ac; return (Node *) ac;
} }
case T_CollateExpr: case T_CollateExpr:
......
...@@ -907,7 +907,12 @@ build_coercion_expression(Node *node, ...@@ -907,7 +907,12 @@ build_coercion_expression(Node *node,
sourceBaseTypeId = getBaseTypeAndTypmod(exprType(node), sourceBaseTypeId = getBaseTypeAndTypmod(exprType(node),
&sourceBaseTypeMod); &sourceBaseTypeMod);
/* Set up CaseTestExpr representing one element of source array */ /*
* Set up a CaseTestExpr representing one element of the source array.
* This is an abuse of CaseTestExpr, but it's OK as long as there
* can't be any CaseExpr or ArrayCoerceExpr within the completed
* elemexpr.
*/
ctest->typeId = get_element_type(sourceBaseTypeId); ctest->typeId = get_element_type(sourceBaseTypeId);
Assert(OidIsValid(ctest->typeId)); Assert(OidIsValid(ctest->typeId));
ctest->typeMod = sourceBaseTypeMod; ctest->typeMod = sourceBaseTypeMod;
......
...@@ -691,7 +691,13 @@ transformAssignmentIndirection(ParseState *pstate, ...@@ -691,7 +691,13 @@ transformAssignmentIndirection(ParseState *pstate,
if (indirection && !basenode) if (indirection && !basenode)
{ {
/* Set up a substitution. We reuse CaseTestExpr for this. */ /*
* Set up a substitution. We abuse CaseTestExpr for this. It's safe
* to do so because the only nodes that will be above the CaseTestExpr
* in the finished expression will be FieldStore and ArrayRef nodes.
* (There could be other stuff in the tree, but it will be within
* other child fields of those node types.)
*/
CaseTestExpr *ctest = makeNode(CaseTestExpr); CaseTestExpr *ctest = makeNode(CaseTestExpr);
ctest->typeId = targetTypeId; ctest->typeId = targetTypeId;
......
...@@ -934,8 +934,20 @@ typedef struct CaseWhen ...@@ -934,8 +934,20 @@ typedef struct CaseWhen
* This is effectively like a Param, but can be implemented more simply * This is effectively like a Param, but can be implemented more simply
* since we need only one replacement value at a time. * since we need only one replacement value at a time.
* *
* We also use this in nested UPDATE expressions. * We also abuse this node type for some other purposes, including:
* See transformAssignmentIndirection(). * * Placeholder for the current array element value in ArrayCoerceExpr;
* see build_coercion_expression().
* * Nested FieldStore/ArrayRef assignment expressions in INSERT/UPDATE;
* see transformAssignmentIndirection().
*
* The uses in CaseExpr and ArrayCoerceExpr are safe only to the extent that
* there is not any other CaseExpr or ArrayCoerceExpr between the value source
* node and its child CaseTestExpr(s). This is true in the parse analysis
* output, but the planner's function-inlining logic has to be careful not to
* break it.
*
* The nested-assignment-expression case is safe because the only node types
* that can be above such CaseTestExprs are FieldStore and ArrayRef.
*/ */
typedef struct CaseTestExpr typedef struct CaseTestExpr
{ {
......
...@@ -372,6 +372,20 @@ SELECT CASE make_ad(1,2) ...@@ -372,6 +372,20 @@ SELECT CASE make_ad(1,2)
right right
(1 row) (1 row)
ROLLBACK;
-- Test interaction of CASE with ArrayCoerceExpr (bug #15471)
BEGIN;
CREATE TYPE casetestenum AS ENUM ('e', 'f', 'g');
SELECT
CASE 'foo'::text
WHEN 'foo' THEN ARRAY['a', 'b', 'c', 'd'] || enum_range(NULL::casetestenum)::text[]
ELSE ARRAY['x', 'y']
END;
array
-----------------
{a,b,c,d,e,f,g}
(1 row)
ROLLBACK; ROLLBACK;
-- --
-- Clean up -- Clean up
......
...@@ -233,6 +233,19 @@ SELECT CASE make_ad(1,2) ...@@ -233,6 +233,19 @@ SELECT CASE make_ad(1,2)
ROLLBACK; ROLLBACK;
-- Test interaction of CASE with ArrayCoerceExpr (bug #15471)
BEGIN;
CREATE TYPE casetestenum AS ENUM ('e', 'f', 'g');
SELECT
CASE 'foo'::text
WHEN 'foo' THEN ARRAY['a', 'b', 'c', 'd'] || enum_range(NULL::casetestenum)::text[]
ELSE ARRAY['x', 'y']
END;
ROLLBACK;
-- --
-- Clean up -- Clean up
-- --
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment