I've had a look at the INNER JOIN case and compared it with a similar, but working, query where VALUE was replaced by MOD. That is
select * from xyz t1 inner join xyz t2 on t1.c1 = t2.c1 where value(t1.c1, t1.c2) = 1
select * from xyz t1 inner join xyz t2 on t1.c1 = t2.c1 where mod(t1.c1, t1.c2) = 1
The node trees for the queries had the exact same structure (except of course that the CoalesceFunctionNode was replaced by a BinaryArithmeticOperatorNode) after the parsing phase and the bind phase. After the optimize phase, there was one difference: The columns under the BinaryArithmeticOperatorNode (for the MOD query) referenced the base table whereas the columns under the CoalesceFunctionNode (for the VALUE query) referenced the result columns from the JoinNode.
At the time when these columns are accessed during execution, no rows have propagated from the base tables to the join results, so the VALUE query gets null instead of a row in getColumnFromRow() and fails with NPE.
The difference between CoalesceFunctionNode and BinaryArithmeticOperatorNode that makes the two queries access the columns differently, is remapColumnReferencesToExpressions(). CFN inherits the method from ValueNode, where it is a no-op. BAON inherits it from BinaryOperatorNode, where remapColumnReferencesToExpressions() is called recursively on the operands.
I tried to add this method to CFN
public ValueNode remapColumnReferencesToExpressions()
for (int i = 0; i < argumentsList.size(); i++)
ValueNode vn = (ValueNode) argumentsList.elementAt(i);
and then the INNER JOIN did not fail and it returned the expected result.
Unfortunately, the RIGHT OUTER JOIN still raised an assert failure, so either that's a separate problem, or adding the method was not the right fix. (I did try to compare the node trees for VALUE vs MOD in the outer join case too, and there the two queries produced completely different trees.)