Skip to content

Commit b58b00e

Browse files
[CALCITE-7394] Nested sub-query with multiple levels of correlation returns incorrect results
1 parent b24f476 commit b58b00e

File tree

3 files changed

+110
-0
lines changed

3 files changed

+110
-0
lines changed

core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,7 @@ private static List<RexNode> fields(RelBuilder builder, int fieldCount) {
884884
private static void matchProject(SubQueryRemoveRule rule,
885885
RelOptRuleCall call) {
886886
final Project project = call.rel(0);
887+
final Set<CorrelationId> projectVariablesSet = project.getVariablesSet();
887888
final RelBuilder builder = call.builder();
888889
final RexSubQuery e =
889890
requireNonNull(RexUtil.SubQueryFinder.find(project.getProjects()));
@@ -894,6 +895,10 @@ private static void matchProject(SubQueryRemoveRule rule,
894895
final int fieldCount = builder.peek().getRowType().getFieldCount();
895896
final Set<CorrelationId> variablesSet =
896897
RelOptUtil.getVariablesUsed(e.rel);
898+
if (!projectVariablesSet.isEmpty()) {
899+
// Only consider the correlated variables which originated from this sub-query level.
900+
variablesSet.retainAll(projectVariablesSet);
901+
}
897902
final RexNode target =
898903
rule.apply(e, variablesSet, logic, builder, 1, fieldCount, 0);
899904
final RexShuttle shuttle = new ReplaceSubQueryShuttle(e, target);

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

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,101 @@ public static Frameworks.ConfigBuilder config() {
208208
assertThat(after, hasTree(planAfter));
209209
}
210210

211+
/** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7057">[CALCITE-7057]
212+
* NPE when decorrelating query containing nested correlated subqueries</a>. */
213+
@Test void test7057() {
214+
final FrameworkConfig frameworkConfig = config().build();
215+
final RelBuilder builder = RelBuilder.create(frameworkConfig);
216+
final RelOptCluster cluster = builder.getCluster();
217+
final Planner planner = Frameworks.getPlanner(frameworkConfig);
218+
final String sql = ""
219+
+ "select\n"
220+
+ " (select ename || ' from dept '\n"
221+
+ " || (select dname from dept where deptno = emp.deptno and emp.empno = empnos.empno)\n"
222+
+ " from emp\n"
223+
+ " ) as ename_from_dept\n"
224+
+ "from (values (7369), (7499)) as empnos(empno) order by 1";
225+
final RelNode originalRel;
226+
try {
227+
final SqlNode parse = planner.parse(sql);
228+
final SqlNode validate = planner.validate(parse);
229+
originalRel = planner.rel(validate).rel;
230+
} catch (Exception e) {
231+
throw TestUtil.rethrow(e);
232+
}
233+
234+
final HepProgram hepProgram = HepProgram.builder()
235+
.addRuleCollection(
236+
ImmutableList.of(
237+
// SubQuery program rules
238+
CoreRules.FILTER_SUB_QUERY_TO_CORRELATE,
239+
CoreRules.PROJECT_SUB_QUERY_TO_CORRELATE,
240+
CoreRules.JOIN_SUB_QUERY_TO_CORRELATE))
241+
.build();
242+
final Program program =
243+
Programs.of(hepProgram, true,
244+
requireNonNull(cluster.getMetadataProvider()));
245+
final RelNode before =
246+
program.run(cluster.getPlanner(), originalRel, cluster.traitSet(),
247+
Collections.emptyList(), Collections.emptyList());
248+
249+
// before fix:
250+
//
251+
// LogicalSort(sort0=[$0], dir0=[ASC])
252+
// LogicalProject(ENAME_FROM_DEPT=[$1])
253+
// LogicalCorrelate(correlation=[$cor2], joinType=[left], requiredColumns=[{0}])
254+
// LogicalValues(tuples=[[{ 7369 }, { 7499 }]])
255+
// LogicalAggregate(group=[{}], agg#0=[SINGLE_VALUE($0)])
256+
// LogicalProject(EXPR$0=[||(||($1, ' from dept '), $8)])
257+
// LogicalJoin(condition=[true], joinType=[left], variablesSet=[[$cor0, $cor2]])
258+
// LogicalTableScan(table=[[scott, EMP]])
259+
// LogicalAggregate(group=[{}], agg#0=[SINGLE_VALUE($0)])
260+
// LogicalProject(DNAME=[$1])
261+
// LogicalFilter(condition=[AND(=($0, $cor0.DEPTNO),
262+
// =(CAST($cor0.EMPNO):INTEGER NOT NULL, $cor2.EMPNO))])
263+
// LogicalTableScan(table=[[scott, DEPT]])
264+
//
265+
final String planBefore = ""
266+
+ "LogicalSort(sort0=[$0], dir0=[ASC])\n"
267+
+ " LogicalProject(ENAME_FROM_DEPT=[$1])\n"
268+
+ " LogicalCorrelate(correlation=[$cor2], joinType=[left], requiredColumns=[{0}])\n"
269+
+ " LogicalValues(tuples=[[{ 7369 }, { 7499 }]])\n"
270+
+ " LogicalAggregate(group=[{}], agg#0=[SINGLE_VALUE($0)])\n"
271+
+ " LogicalProject(EXPR$0=[||(||($1, ' from dept '), $8)])\n"
272+
+ " LogicalCorrelate(correlation=[$cor0], joinType=[left], requiredColumns=[{0, 7}])\n"
273+
+ " LogicalTableScan(table=[[scott, EMP]])\n"
274+
+ " LogicalAggregate(group=[{}], agg#0=[SINGLE_VALUE($0)])\n"
275+
+ " LogicalProject(DNAME=[$1])\n"
276+
+ " LogicalFilter(condition=[AND(=($0, $cor0.DEPTNO), =(CAST($cor0.EMPNO):INTEGER NOT NULL, $cor2.EMPNO))])\n"
277+
+ " LogicalTableScan(table=[[scott, DEPT]])\n";
278+
assertThat(before, hasTree(planBefore));
279+
280+
// Decorrelate without any rules, just "purely" decorrelation algorithm on RelDecorrelator
281+
final RelNode after =
282+
RelDecorrelator.decorrelateQuery(before, builder, RuleSets.ofList(Collections.emptyList()),
283+
RuleSets.ofList(Collections.emptyList()));
284+
final String planAfter = ""
285+
+ "LogicalSort(sort0=[$0], dir0=[ASC])\n"
286+
+ " LogicalProject(ENAME_FROM_DEPT=[$2])\n"
287+
+ " LogicalJoin(condition=[IS NOT DISTINCT FROM($0, $1)], joinType=[left])\n"
288+
+ " LogicalValues(tuples=[[{ 7369 }, { 7499 }]])\n"
289+
+ " LogicalAggregate(group=[{0}], agg#0=[SINGLE_VALUE($1)])\n"
290+
+ " LogicalProject(EMPNO1=[$12], EXPR$0=[||(||($1, ' from dept '), $13)])\n"
291+
+ " LogicalJoin(condition=[AND(=($7, $10), =($9, $11))], joinType=[left])\n"
292+
+ " LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], DEPTNO0=[$7], EMPNO0=[CAST($0):INTEGER NOT NULL])\n"
293+
+ " LogicalTableScan(table=[[scott, EMP]])\n"
294+
+ " LogicalAggregate(group=[{0, 1, 2}], agg#0=[SINGLE_VALUE($3)])\n"
295+
+ " LogicalProject(DEPTNO0=[$3], EMPNO0=[$4], EMPNO=[$5], DNAME=[$1])\n"
296+
+ " LogicalJoin(condition=[=($0, $3)], joinType=[inner])\n"
297+
+ " LogicalTableScan(table=[[scott, DEPT]])\n"
298+
+ " LogicalJoin(condition=[=($1, $2)], joinType=[inner])\n"
299+
+ " LogicalAggregate(group=[{0, 1}])\n"
300+
+ " LogicalProject(DEPTNO=[$7], EMPNO0=[CAST($0):INTEGER NOT NULL])\n"
301+
+ " LogicalTableScan(table=[[scott, EMP]])\n"
302+
+ " LogicalValues(tuples=[[{ 7369 }, { 7499 }]])\n";
303+
assertThat(after, hasTree(planAfter));
304+
}
305+
211306
@Test void testDecorrelateCountBug() {
212307
final FrameworkConfig frameworkConfig = config().build();
213308
final RelBuilder builder = RelBuilder.create(frameworkConfig);

core/src/test/resources/sql/scalar.iq

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,4 +451,14 @@ EnumerableCalc(expr#0..10=[{inputs}], EMPNO=[$t0], $f1=[$t10])
451451
# Reset to default value true
452452
!set trimfields true
453453

454+
# [CALCITE-7057] NPE when decorrelating query containing nested correlated subqueries
455+
# Nested scalar sub-queries
456+
select
457+
(select ename || ' from dept '
458+
|| (select dname from dept where deptno = emp.deptno and emp.empno = empnos.empno)
459+
from emp
460+
) as ename_from_dept
461+
from (values (7369), (7499)) as empnos(empno) order by 1;
462+
more than one value in agg SINGLE_VALUE
463+
!error
454464
# End scalar.iq

0 commit comments

Comments
 (0)