@@ -356,6 +356,138 @@ public static Frameworks.ConfigBuilder config() {
356356 assertThat (after , hasTree (planAfter ));
357357 }
358358
359+ /** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7394">[CALCITE-7394]
360+ * Nested sub-query with multiple levels of correlation returns incorrect results</a>. */
361+ @ Test void test7394 () {
362+ final FrameworkConfig frameworkConfig = config ().build ();
363+ final RelBuilder builder = RelBuilder .create (frameworkConfig );
364+ final RelOptCluster cluster = builder .getCluster ();
365+ final Planner planner = Frameworks .getPlanner (frameworkConfig );
366+ final String sql = ""
367+ + "select d.dname,\n "
368+ + " (select count(*)\n "
369+ + " from emp e\n "
370+ + " where e.deptno = d.deptno\n "
371+ + " and exists (\n "
372+ + " select 1\n "
373+ + " from (values (1000), (2000), (3000)) as v(sal)\n "
374+ + " where e.sal > v.sal\n "
375+ + " and d.deptno * 100 < v.sal\n "
376+ + " )\n "
377+ + " ) as c\n "
378+ + "from dept d\n "
379+ + "order by d.dname" ;
380+ final RelNode originalRel ;
381+ try {
382+ final SqlNode parse = planner .parse (sql );
383+ final SqlNode validate = planner .validate (parse );
384+ originalRel = planner .rel (validate ).rel ;
385+ } catch (Exception e ) {
386+ throw TestUtil .rethrow (e );
387+ }
388+
389+ final HepProgram hepProgram = HepProgram .builder ()
390+ .addRuleCollection (
391+ ImmutableList .of (
392+ // SubQuery program rules
393+ CoreRules .FILTER_SUB_QUERY_TO_CORRELATE ,
394+ CoreRules .PROJECT_SUB_QUERY_TO_CORRELATE ,
395+ CoreRules .JOIN_SUB_QUERY_TO_CORRELATE ))
396+ .build ();
397+ final Program program =
398+ Programs .of (hepProgram , true ,
399+ requireNonNull (cluster .getMetadataProvider ()));
400+ final RelNode before =
401+ program .run (cluster .getPlanner (), originalRel , cluster .traitSet (),
402+ Collections .emptyList (), Collections .emptyList ());
403+ final String planBefore = ""
404+ + "LogicalSort(sort0=[$0], dir0=[ASC])\n "
405+ + " LogicalProject(DNAME=[$1], C=[$3])\n "
406+ + " LogicalCorrelate(correlation=[$cor0], joinType=[left], requiredColumns=[{0}])\n "
407+ + " LogicalTableScan(table=[[scott, DEPT]])\n "
408+ + " LogicalAggregate(group=[{}], EXPR$0=[COUNT()])\n "
409+ + " LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7])\n "
410+ + " LogicalFilter(condition=[=($7, $cor0.DEPTNO)])\n "
411+ + " LogicalCorrelate(correlation=[$cor1], joinType=[inner], requiredColumns=[{5}])\n "
412+ + " LogicalTableScan(table=[[scott, EMP]])\n "
413+ + " LogicalAggregate(group=[{0}])\n "
414+ + " LogicalProject(i=[true])\n "
415+ + " LogicalFilter(condition=[AND(>(CAST($cor1.SAL):DECIMAL(12, 2), CAST($0):DECIMAL(12, 2) NOT NULL), <(*($cor0.DEPTNO, 100), $0))])\n "
416+ + " LogicalValues(tuples=[[{ 1000 }, { 2000 }, { 3000 }]])\n " ;
417+ assertThat (before , hasTree (planBefore ));
418+
419+ // Decorrelate without any rules, just "purely" decorrelation algorithm on RelDecorrelator
420+ final RelNode after =
421+ RelDecorrelator .decorrelateQuery (before , builder , RuleSets .ofList (Collections .emptyList ()),
422+ RuleSets .ofList (Collections .emptyList ()));
423+ // before fix:
424+ //
425+ // LogicalSort(sort0=[$0], dir0=[ASC])
426+ // LogicalProject(DNAME=[$1], C=[$7])
427+ // LogicalJoin(condition=[AND(=($0, $5), =($4, $6))], joinType=[left])
428+ // LogicalProject(DEPTNO=[$0], DNAME=[$1], LOC=[$2], DEPTNO0=[$0], $f4=[*($0, 100)])
429+ // LogicalTableScan(table=[[scott, DEPT]])
430+ // LogicalProject(DEPTNO8=[$0], $f4=[$1], EXPR$0=[CASE(IS NOT NULL($5), $5, 0)])
431+ // LogicalJoin(condition=[AND(IS NOT DISTINCT FROM($0, $3),
432+ // IS NOT DISTINCT FROM($1, $4))], joinType=[left])
433+ // LogicalJoin(condition=[true], joinType=[inner]) // <---- error part
434+ // LogicalProject(DEPTNO=[$0], $f4=[*($0, 100)])
435+ // LogicalTableScan(table=[[scott, DEPT]])
436+ // LogicalAggregate(group=[{0}]) // <---- error part
437+ // LogicalProject(SAL0=[CAST($5):DECIMAL(12, 2)]) // <---- error part
438+ // LogicalTableScan(table=[[scott, EMP]]) // <---- error part
439+ // LogicalAggregate(group=[{0, 1}], EXPR$0=[COUNT()])
440+ // LogicalProject(DEPTNO8=[$7], $f4=[$9])
441+ // LogicalFilter(condition=[IS NOT NULL($7)])
442+ // LogicalProject(..., DEPTNO=[$7], i=[$11], $f4=[$9])
443+ // LogicalJoin(condition=[=($8, $10)], joinType=[inner])
444+ // LogicalProject(..., SAL0=[CAST($5):DECIMAL(12, 2)])
445+ // LogicalTableScan(table=[[scott, EMP]])
446+ // LogicalProject($f4=[$0], SAL0=[$1], $f2=[true])
447+ // LogicalAggregate(group=[{0, 1}])
448+ // LogicalProject($f4=[$1], SAL0=[$2])
449+ // LogicalJoin(condition=[AND(>($2, CAST($0):DECIMAL(12, 2) NOT NULL),
450+ // <($1, $0))], joinType=[inner])
451+ // LogicalValues(tuples=[[{ 1000 }, { 2000 }, { 3000 }]])
452+ // LogicalJoin(condition=[true], joinType=[inner])
453+ // LogicalAggregate(group=[{0}])
454+ // LogicalProject($f4=[*($0, 100)])
455+ // LogicalTableScan(table=[[scott, DEPT]])
456+ // LogicalAggregate(group=[{0}])
457+ // LogicalProject(SAL0=[CAST($5):DECIMAL(12, 2)])
458+ // LogicalTableScan(table=[[scott, EMP]])
459+ final String planAfter = ""
460+ + "LogicalSort(sort0=[$0], dir0=[ASC])\n "
461+ + " LogicalProject(DNAME=[$1], C=[$7])\n "
462+ + " LogicalJoin(condition=[AND(=($0, $5), =($4, $6))], joinType=[left])\n "
463+ + " LogicalProject(DEPTNO=[$0], DNAME=[$1], LOC=[$2], DEPTNO0=[$0], $f4=[*($0, 100)])\n "
464+ + " LogicalTableScan(table=[[scott, DEPT]])\n "
465+ + " LogicalProject(DEPTNO8=[$0], $f4=[$1], EXPR$0=[CASE(IS NOT NULL($4), $4, 0)])\n "
466+ + " LogicalJoin(condition=[AND(IS NOT DISTINCT FROM($0, $2), IS NOT DISTINCT FROM($1, $3))], joinType=[left])\n "
467+ + " LogicalProject(DEPTNO=[$0], $f4=[*($0, 100)])\n "
468+ + " LogicalTableScan(table=[[scott, DEPT]])\n "
469+ + " LogicalAggregate(group=[{0, 1}], EXPR$0=[COUNT()])\n "
470+ + " LogicalProject(DEPTNO8=[$7], $f4=[$9])\n "
471+ + " LogicalFilter(condition=[IS NOT NULL($7)])\n "
472+ + " LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], i=[$11], $f4=[$9])\n "
473+ + " LogicalJoin(condition=[=($8, $10)], joinType=[inner])\n "
474+ + " LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SAL0=[CAST($5):DECIMAL(12, 2)])\n "
475+ + " LogicalTableScan(table=[[scott, EMP]])\n "
476+ + " LogicalProject($f4=[$0], SAL0=[$1], $f2=[true])\n "
477+ + " LogicalAggregate(group=[{0, 1}])\n "
478+ + " LogicalProject($f4=[$1], SAL0=[$2])\n "
479+ + " LogicalJoin(condition=[AND(>($2, CAST($0):DECIMAL(12, 2) NOT NULL), <($1, $0))], joinType=[inner])\n "
480+ + " LogicalValues(tuples=[[{ 1000 }, { 2000 }, { 3000 }]])\n "
481+ + " LogicalJoin(condition=[true], joinType=[inner])\n "
482+ + " LogicalAggregate(group=[{0}])\n "
483+ + " LogicalProject($f4=[*($0, 100)])\n "
484+ + " LogicalTableScan(table=[[scott, DEPT]])\n "
485+ + " LogicalAggregate(group=[{0}])\n "
486+ + " LogicalProject(SAL0=[CAST($5):DECIMAL(12, 2)])\n "
487+ + " LogicalTableScan(table=[[scott, EMP]])\n " ;
488+ assertThat (after , hasTree (planAfter ));
489+ }
490+
359491 /** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7297">[CALCITE-7297]
360492 * The result is incorrect when the GROUP BY key in a subquery is a RexFieldAccess</a>. */
361493 @ Test void testSkipsRedundantValueGenerator () {
0 commit comments