Skip to content

Commit a4571f9

Browse files
[CALCITE-7257] Subqueries cannot be decorrelated if join condition contains RexFieldAccess
1 parent 081e442 commit a4571f9

File tree

3 files changed

+428
-6
lines changed

3 files changed

+428
-6
lines changed

core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1772,8 +1772,11 @@ private static boolean isWidening(RelDataType type, RelDataType type1) {
17721772
return null;
17731773
}
17741774

1775+
1776+
Frame newLeftFrame = getNewLeftFrame(rel, oldLeft, leftFrame, isCorVarDefined);
1777+
17751778
RelNode newJoin = relBuilder
1776-
.push(leftFrame.r)
1779+
.push(newLeftFrame.r)
17771780
.push(rightFrame.r)
17781781
.join(rel.getJoinType(),
17791782
decorrelateExpr(castNonNull(currentRel), map, cm, rel.getCondition()),
@@ -1785,25 +1788,23 @@ private static boolean isWidening(RelDataType type, RelDataType type1) {
17851788
Map<Integer, Integer> mapOldToNewOutputs = new HashMap<>();
17861789

17871790
int oldLeftFieldCount = oldLeft.getRowType().getFieldCount();
1788-
int newLeftFieldCount = leftFrame.r.getRowType().getFieldCount();
1791+
int newLeftFieldCount = newLeftFrame.r.getRowType().getFieldCount();
17891792

17901793
int oldRightFieldCount = oldRight.getRowType().getFieldCount();
17911794
//noinspection AssertWithSideEffects
17921795
assert rel.getRowType().getFieldCount()
17931796
== oldLeftFieldCount + oldRightFieldCount;
17941797

17951798
// Left input positions are not changed.
1796-
mapOldToNewOutputs.putAll(leftFrame.oldToNewOutputs);
1797-
1799+
mapOldToNewOutputs.putAll(newLeftFrame.oldToNewOutputs);
17981800
// Right input positions are shifted by newLeftFieldCount.
17991801
for (int i = 0; i < oldRightFieldCount; i++) {
18001802
mapOldToNewOutputs.put(i + oldLeftFieldCount,
18011803
requireNonNull(rightFrame.oldToNewOutputs.get(i)) + newLeftFieldCount);
18021804
}
18031805

18041806
final NavigableMap<CorDef, Integer> corDefOutputs =
1805-
new TreeMap<>(leftFrame.corDefOutputs);
1806-
1807+
new TreeMap<>(newLeftFrame.corDefOutputs);
18071808
// Right input positions are shifted by newLeftFieldCount.
18081809
for (Map.Entry<CorDef, Integer> entry
18091810
: rightFrame.corDefOutputs.entrySet()) {
@@ -1813,6 +1814,46 @@ private static boolean isWidening(RelDataType type, RelDataType type1) {
18131814
return register(rel, newJoin, mapOldToNewOutputs, corDefOutputs);
18141815
}
18151816

1817+
private Frame getNewLeftFrame(Join rel, RelNode oldLeft, Frame leftFrame,
1818+
boolean isCorVarDefined) {
1819+
Frame newLeftFrame = leftFrame;
1820+
boolean joinConditionContainsFieldAccess = RexUtil.containsFieldAccess(rel.getCondition());
1821+
if (joinConditionContainsFieldAccess && isCorVarDefined) {
1822+
final CorelMap localCorelMap = new CorelMapBuilder().build(rel);
1823+
final List<CorRef> corVarList = new ArrayList<>(localCorelMap.mapRefRelToCorRef.values());
1824+
Collections.sort(corVarList);
1825+
1826+
// 1. Build valueGen and a new corDefOutputs based on corVarList.
1827+
final NavigableMap<CorDef, Integer> newLeftCorDefOutputs = new TreeMap<>();
1828+
final RelNode valueGen =
1829+
requireNonNull(createValueGenerator(corVarList, 0, newLeftCorDefOutputs));
1830+
int valueGenFieldCount = valueGen.getRowType().getFieldCount();
1831+
RelNode newLeft = relBuilder
1832+
.push(valueGen)
1833+
.push(leftFrame.r)
1834+
.join(JoinRelType.INNER, relBuilder.literal(true))
1835+
.build();
1836+
1837+
// 3. Merge leftFrame.corDefOutputs into newLeftCorDefOutputs, with valueGenFieldCount offset
1838+
for (Map.Entry<CorDef,Integer> e : leftFrame.corDefOutputs.entrySet()) {
1839+
newLeftCorDefOutputs.put(e.getKey(), valueGenFieldCount + e.getValue());
1840+
}
1841+
1842+
// 4. Compute the new positions of oldLeft's oldToNewOutputs in newLeft.
1843+
final Map<Integer,Integer> newLeftOldToNew = new HashMap<>();
1844+
for (Map.Entry<Integer,Integer> e : leftFrame.oldToNewOutputs.entrySet()) {
1845+
newLeftOldToNew.put(e.getKey(), valueGenFieldCount + e.getValue());
1846+
}
1847+
1848+
// 5. Build a new Frame with oldLeft, newLeft, and the mappings above.
1849+
newLeftFrame = new Frame(oldLeft, newLeft, newLeftCorDefOutputs, newLeftOldToNew);
1850+
1851+
// 6. Update map so that oldLeft is associated with the new Frame.
1852+
map.put(oldLeft, newLeftFrame);
1853+
}
1854+
return newLeftFrame;
1855+
}
1856+
18161857
private static RexInputRef getNewForOldInputRef(RelNode currentRel,
18171858
Map<RelNode, Frame> map, RexInputRef oldInputRef) {
18181859
requireNonNull(currentRel, "currentRel");

core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,4 +584,78 @@ public static Frameworks.ConfigBuilder config() {
584584
+ " LogicalTableScan(table=[[scott, DEPT]])\n";
585585
assertThat(decorrelatedNoRules, hasTree(planDecorrelatedNoRules));
586586
}
587+
588+
/** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7257">[CALCITE-7257]
589+
* Subqueries cannot be decorrelated if join condition contains RexFieldAccess</a>. */
590+
@Test void testJoinConditionContainsRexFieldAccess() {
591+
final FrameworkConfig frameworkConfig = config().build();
592+
final RelBuilder builder = RelBuilder.create(frameworkConfig);
593+
final RelOptCluster cluster = builder.getCluster();
594+
final Planner planner = Frameworks.getPlanner(frameworkConfig);
595+
final String sql = ""
596+
+ "SELECT E1.* \n"
597+
+ "FROM\n"
598+
+ " EMP E1\n"
599+
+ "WHERE\n"
600+
+ " E1.EMPNO = (\n"
601+
+ " SELECT D1.DEPTNO FROM DEPT D1\n"
602+
+ " WHERE E1.ENAME IN (SELECT B1.ENAME FROM BONUS B1))";
603+
final RelNode originalRel;
604+
try {
605+
final SqlNode parse = planner.parse(sql);
606+
final SqlNode validate = planner.validate(parse);
607+
originalRel = planner.rel(validate).rel;
608+
} catch (Exception e) {
609+
throw TestUtil.rethrow(e);
610+
}
611+
612+
final HepProgram hepProgram = HepProgram.builder()
613+
.addRuleCollection(
614+
ImmutableList.of(
615+
// SubQuery program rules
616+
CoreRules.FILTER_SUB_QUERY_TO_CORRELATE,
617+
CoreRules.PROJECT_SUB_QUERY_TO_CORRELATE,
618+
CoreRules.JOIN_SUB_QUERY_TO_CORRELATE))
619+
.build();
620+
final Program program =
621+
Programs.of(hepProgram, true,
622+
requireNonNull(cluster.getMetadataProvider()));
623+
final RelNode before =
624+
program.run(cluster.getPlanner(), originalRel, cluster.traitSet(),
625+
Collections.emptyList(), Collections.emptyList());
626+
final String planBefore = ""
627+
+ "LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7])\n"
628+
+ " LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7])\n"
629+
+ " LogicalFilter(condition=[=($0, CAST($8):SMALLINT)])\n"
630+
+ " LogicalCorrelate(correlation=[$cor0], joinType=[left], requiredColumns=[{1}])\n"
631+
+ " LogicalTableScan(table=[[scott, EMP]])\n"
632+
+ " LogicalAggregate(group=[{}], agg#0=[SINGLE_VALUE($0)])\n"
633+
+ " LogicalProject(DEPTNO=[$0])\n"
634+
+ " LogicalProject(DEPTNO=[$0], DNAME=[$1], LOC=[$2])\n"
635+
+ " LogicalJoin(condition=[=($cor0.ENAME, $3)], joinType=[inner])\n"
636+
+ " LogicalTableScan(table=[[scott, DEPT]])\n"
637+
+ " LogicalProject(ENAME=[$0])\n"
638+
+ " LogicalTableScan(table=[[scott, BONUS]])\n";
639+
assertThat(before, hasTree(planBefore));
640+
641+
// Decorrelate without any rules, just "purely" decorrelation algorithm on RelDecorrelator
642+
final RelNode after =
643+
RelDecorrelator.decorrelateQuery(before, builder, RuleSets.ofList(Collections.emptyList()),
644+
RuleSets.ofList(Collections.emptyList()));
645+
final String planAfter = ""
646+
+ "LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7])\n"
647+
+ " LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], ENAME0=[$8], $f1=[CAST($9):TINYINT])\n"
648+
+ " LogicalJoin(condition=[AND(=($1, $8), =($0, CAST($9):SMALLINT))], joinType=[inner])\n"
649+
+ " LogicalTableScan(table=[[scott, EMP]])\n"
650+
+ " LogicalAggregate(group=[{0}], agg#0=[SINGLE_VALUE($1)])\n"
651+
+ " LogicalProject(ENAME=[$0], DEPTNO=[$1])\n"
652+
+ " LogicalJoin(condition=[=($0, $4)], joinType=[inner])\n"
653+
+ " LogicalJoin(condition=[true], joinType=[inner])\n"
654+
+ " LogicalProject(ENAME=[$1])\n"
655+
+ " LogicalTableScan(table=[[scott, EMP]])\n"
656+
+ " LogicalTableScan(table=[[scott, DEPT]])\n"
657+
+ " LogicalProject(ENAME=[$0])\n"
658+
+ " LogicalTableScan(table=[[scott, BONUS]])\n";
659+
assertThat(after, hasTree(planAfter));
660+
}
587661
}

0 commit comments

Comments
 (0)