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 c12d570fa:
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
This commit is contained in:
Tom Lane 2018-10-30 15:26:11 -04:00
parent c2c7c263af
commit 14a158f9bf
7 changed files with 108 additions and 16 deletions

View File

@ -1516,10 +1516,11 @@ ExecInitExprRec(Expr *node, ExprState *state,
/*
* Read from location identified by innermost_caseval. Note
* that innermost_caseval could be NULL, if this node isn't
* actually within a CASE structure; some parts of the system
* abuse CaseTestExpr to cause a read of a value externally
* supplied in econtext->caseValue_datum. We'll take care of
* that scenario at runtime.
* actually within a CaseExpr, ArrayCoerceExpr, etc structure.
* That can happen because some parts of the system abuse
* CaseTestExpr to cause a read of a value externally supplied
* in econtext->caseValue_datum. We'll take care of that
* scenario at runtime.
*/
scratch.opcode = EEOP_CASE_TESTVAL;
scratch.d.casetest.value = state->innermost_caseval;

View File

@ -1452,7 +1452,8 @@ contain_nonstrict_functions_walker(Node *node, void *context)
* CaseTestExpr nodes must appear directly within the corresponding CaseExpr,
* 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
* 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
* could get inlined into larger expressions, but presently that's impossible.
@ -1468,7 +1469,7 @@ contain_context_dependent_node(Node *clause)
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
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)
return false;
if (IsA(node, CaseTestExpr))
return !(*flags & CCDN_IN_CASEEXPR);
if (IsA(node, CaseExpr))
return !(*flags & CCDN_CASETESTEXPR_OK);
else if (IsA(node, CaseExpr))
{
CaseExpr *caseexpr = (CaseExpr *) node;
@ -1499,7 +1500,7 @@ contain_context_dependent_node_walker(Node *node, int *flags)
* seem worth any extra code. If there are any bare CaseTestExprs
* elsewhere in the CASE, something's wrong already.
*/
*flags |= CCDN_IN_CASEEXPR;
*flags |= CCDN_CASETESTEXPR_OK;
res = expression_tree_walker(node,
contain_context_dependent_node_walker,
(void *) flags);
@ -1507,6 +1508,24 @@ contain_context_dependent_node_walker(Node *node, int *flags)
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,
(void *) flags);
}
@ -3125,10 +3144,31 @@ eval_const_expressions_mutator(Node *node,
}
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
@ -3142,6 +3182,7 @@ eval_const_expressions_mutator(Node *node,
ac->elemexpr && !IsA(ac->elemexpr, CoerceToDomain) &&
!contain_mutable_functions((Node *) ac->elemexpr))
return ece_evaluate_expr(ac);
return (Node *) ac;
}
case T_CollateExpr:

View File

@ -907,7 +907,12 @@ build_coercion_expression(Node *node,
sourceBaseTypeId = getBaseTypeAndTypmod(exprType(node),
&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);
Assert(OidIsValid(ctest->typeId));
ctest->typeMod = sourceBaseTypeMod;

View File

@ -691,7 +691,13 @@ transformAssignmentIndirection(ParseState *pstate,
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);
ctest->typeId = targetTypeId;

View File

@ -934,8 +934,20 @@ typedef struct CaseWhen
* This is effectively like a Param, but can be implemented more simply
* since we need only one replacement value at a time.
*
* We also use this in nested UPDATE expressions.
* See transformAssignmentIndirection().
* We also abuse this node type for some other purposes, including:
* * 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
{

View File

@ -372,6 +372,20 @@ SELECT CASE make_ad(1,2)
right
(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;
--
-- Clean up

View File

@ -233,6 +233,19 @@ SELECT CASE make_ad(1,2)
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
--