Skip to content

Commit 6d02cd3

Browse files
[CALCITE-7057] NPE when decorrelating query containing nested correlated subqueries
1 parent f451b2a commit 6d02cd3

File tree

3 files changed

+93
-0
lines changed

3 files changed

+93
-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: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,84 @@ 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+
final String planBefore = ""
249+
+ "LogicalSort(sort0=[$0], dir0=[ASC])\n"
250+
+ " LogicalProject(ENAME_FROM_DEPT=[$1])\n"
251+
+ " LogicalCorrelate(correlation=[$cor2], joinType=[left], requiredColumns=[{0}])\n"
252+
+ " LogicalValues(tuples=[[{ 7369 }, { 7499 }]])\n"
253+
+ " LogicalAggregate(group=[{}], agg#0=[SINGLE_VALUE($0)])\n"
254+
+ " LogicalProject(EXPR$0=[||(||($1, ' from dept '), $8)])\n"
255+
+ " LogicalCorrelate(correlation=[$cor0], joinType=[left], requiredColumns=[{0, 7}])\n"
256+
+ " LogicalTableScan(table=[[scott, EMP]])\n"
257+
+ " LogicalAggregate(group=[{}], agg#0=[SINGLE_VALUE($0)])\n"
258+
+ " LogicalProject(DNAME=[$1])\n"
259+
+ " LogicalFilter(condition=[AND(=($0, $cor0.DEPTNO), =(CAST($cor0.EMPNO):INTEGER NOT NULL, $cor2.EMPNO))])\n"
260+
+ " LogicalTableScan(table=[[scott, DEPT]])\n";
261+
assertThat(before, hasTree(planBefore));
262+
263+
// Decorrelate without any rules, just "purely" decorrelation algorithm on RelDecorrelator
264+
final RelNode after =
265+
RelDecorrelator.decorrelateQuery(before, builder, RuleSets.ofList(Collections.emptyList()),
266+
RuleSets.ofList(Collections.emptyList()));
267+
final String planAfter = ""
268+
+ "LogicalSort(sort0=[$0], dir0=[ASC])\n"
269+
+ " LogicalProject(ENAME_FROM_DEPT=[$2])\n"
270+
+ " LogicalJoin(condition=[IS NOT DISTINCT FROM($0, $1)], joinType=[left])\n"
271+
+ " LogicalValues(tuples=[[{ 7369 }, { 7499 }]])\n"
272+
+ " LogicalAggregate(group=[{0}], agg#0=[SINGLE_VALUE($1)])\n"
273+
+ " LogicalProject(EMPNO1=[$12], EXPR$0=[||(||($1, ' from dept '), $13)])\n"
274+
+ " LogicalJoin(condition=[AND(=($7, $10), =($9, $11))], joinType=[left])\n"
275+
+ " 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"
276+
+ " LogicalTableScan(table=[[scott, EMP]])\n"
277+
+ " LogicalAggregate(group=[{0, 1, 2}], agg#0=[SINGLE_VALUE($3)])\n"
278+
+ " LogicalProject(DEPTNO0=[$3], EMPNO0=[$4], EMPNO=[$5], DNAME=[$1])\n"
279+
+ " LogicalJoin(condition=[=($0, $3)], joinType=[inner])\n"
280+
+ " LogicalTableScan(table=[[scott, DEPT]])\n"
281+
+ " LogicalJoin(condition=[=($1, $2)], joinType=[inner])\n"
282+
+ " LogicalAggregate(group=[{0, 1}])\n"
283+
+ " LogicalProject(DEPTNO=[$7], EMPNO0=[CAST($0):INTEGER NOT NULL])\n"
284+
+ " LogicalTableScan(table=[[scott, EMP]])\n"
285+
+ " LogicalValues(tuples=[[{ 7369 }, { 7499 }]])\n";
286+
assertThat(after, hasTree(planAfter));
287+
}
288+
211289
@Test void testDecorrelateCountBug() {
212290
final FrameworkConfig frameworkConfig = config().build();
213291
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)