Repository navigation
[CALCITE-7821] Correlated query crashes SqlToRelConverter - #5291
mihaibudiu wants to merge 3 commits into
Conversation
vlsi
left a comment
There was a problem hiding this comment.
The fix works for the query in the test: it fails on main with AssertionError: contains $cor0 and passes here. I ran seven correlated NOT IN queries with expand=true and compared their results with expand=false, and all seven matched. They covered multi-column keys, the SELECT list, two NOT IN in one WHERE, nesting inside EXISTS, NOT (x IN …), and an expression key. Three other points are inline: the same crash in HAVING, a comment that became wrong once it moved above the switch, and missing test shapes.
The ON clause is broken separately, and this PR only moves the crash. On this branch, select * from emp as e join dept as d on e.mgr not in (select s.mgr from emp as s where s.deptno = d.deptno) now fails in the new createJoin with Required columns {9} not subset of left columns {0, 1, 2, 3, 4, 5, 6, 7, 8}. The uncorrelated on e.mgr not in (select s.mgr from emp as s) already fails on main with ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 4. The cause is that the count join is not added to bb.registered, so convertOnCondition drops it when it calls reRegister(rightRel). That is probably a separate JIRA, but then the description should not say that both node kinds are handled "uniformly".
About the description: on main the crash is not in translateIn. It happens in Blackboard.register → LogicalCorrelate.isValid, because the count join was built with LogicalJoin.create and left $cor0 unbound in its right input. Suggested wording: "The count sub-query that NOT IN joins to the outer query was built with LogicalJoin.create, which leaves its correlation variable unbound, so SqlToRelConverter failed with "contains $cor0" when it correlated the second join. The count join is now built by createJoin, which returns a Correlate for a correlated sub-query, and translateIn accepts either join kind."
| final RelNode join = | ||
| createJoin(bb, bb.root(), aggregate, rexBuilder.makeLiteral(true), | ||
| JoinRelType.INNER); | ||
| bb.setRoot(join, false); |
There was a problem hiding this comment.
The same crash still happens when the outer query aggregates:
select deptno from emp as e group by deptno
having deptno not in (select s.mgr from emp as s where s.deptno = e.deptno)It fails with AssertionError: Required columns {3, 7} not subset of left columns {0, 1, 2, 3} from Blackboard.register → createJoin. The IN variant and the uncorrelated NOT IN variant both work.
The new count join becomes bb.root, but it has no entry in mapRootRelToFieldProjection. As a result, getCorrelationUse for the second join takes the column positions from before the aggregate. With the following lines before bb.setRoot, the query produces a valid plan both with and without decorrelation:
final Map<Integer, Integer> projection =
bb.mapRootRelToFieldProjection.get(bb.root());
if (projection != null) {
bb.mapRootRelToFieldProjection.put(join, projection);
}Could you add this query as a test too?
| LogicalJoin join = | ||
| LogicalJoin.create(bb.root(), aggregate, ImmutableList.of(), | ||
| rexBuilder.makeLiteral(true), ImmutableSet.of(), JoinRelType.INNER); | ||
| // createJoin will create a Correlate node if a sub-query is correlated |
There was a problem hiding this comment.
| // createJoin will create a Correlate node if a sub-query is correlated | |
| // seek may reference the outer query; createJoin then returns | |
| // a Correlate that binds the variable. |
The comment uses "will" for current behavior, and it describes what createJoin does rather than why it is called here instead of LogicalJoin.create.
| // RexRangeRef contains the following fields: | ||
| // leftKeysForIn, | ||
| // rightKeysForIn (the original sub-query select list), | ||
| // nullIndicator | ||
| // | ||
| // The first two lists contain the same number of fields. | ||
| assert rex instanceof RexRangeRef; | ||
| final int fieldCount = rex.getType().getFieldCount(); | ||
| final int keyCount = (fieldCount - 1) / 2; |
There was a problem hiding this comment.
This layout (leftKeysForIn, rightKeysForIn, nullIndicator) holds only when register gets LEFT and leftKeys. For TRUE, and for an INNER register, rex covers the sub-query row type alone, and keyCount means nothing there. Now that the comment and keyCount sit above the switch, they claim the layout for every branch. Could they stay inside the two branches that use them, or state the condition?
| // left join (select distinct deptno, true as i from v) as dt | ||
| // on e.deptno = dt.deptno | ||
| final Join join = (Join) requireNonNull(root, "root"); | ||
| // The joins may be Correlate nodes if the sub-query is correlated. |
There was a problem hiding this comment.
Nit: "Either join is a Correlate when the sub-query is correlated." is more precise. "The joins may be" leaves it unclear which joins.
| // Read the left keys from rex; if 'join' is a Correlate | ||
| // it has no join condition |
There was a problem hiding this comment.
| // Read the left keys from rex; if 'join' is a Correlate | |
| // it has no join condition | |
| // Take the IN keys from rex rather than from the join condition, | |
| // because a Correlate has no condition. |
| /** Test case for | ||
| * <a href="https://issues.apache.org/jira/browse/CALCITE-7821">[CALCITE-7821] | ||
| * Correlated query crashes SqlToRelConverter</a>. */ | ||
| @Test void testNotInCorrelatedNullable() { |
There was a problem hiding this comment.
The checked-in plan is the decorrelated one, so the shape that translateIn now handles (two nested LogicalCorrelate) is not in any snapshot. Could you add a .withDecorrelate(false) variant? A multi-column case is also worth a test, such as (t.mgr, t.deptno) not in (select s.mgr, s.deptno from emp as s where s.empno = t.empno). That case exercises the replacement of joinInfo.leftKeys with rex fields. I checked its plan and results and found them correct, but nothing guards it.
|
The tests cover one variant of each thing this change touched. These cases are missing:
|
|
Some of these extra tests would be nice, but they are not really in scope of the issue being solved. |
|
Thanks for the HAVING fix and for taking the comment suggestions. 8f2ab7d adds the You wrote: "Some of these extra tests would be nice, but they are not really in scope of the issue being solved." Which of the four do you consider out of scope, and why? I agree for some of them and not for others, so I'd like to understand where you draw the line. Here is my view of each:
On the ON clause: I agree it is a separate issue. Is it the follow-up you plan to file? If not, I can file it. The PR description still says that |
Signed-off-by: Mihai Budiu <mbudiu@feldera.com>
Signed-off-by: Mihai Budiu <mbudiu@feldera.com>
|
The composite key program returns wrong results, so it's a separate issue I need to file. |
Signed-off-by: Mihai Budiu <mbudiu@feldera.com>
|
@vlsi I have pushed another commit with an answer to your request 2. |
|



Jira Link
CALCITE-7821
Changes Proposed
SqlToRelConverter.translateInwas assuming that the Join nodes are never Correlates. The changes should handle both Join and Correlate nodes uniformly.