blob: c1e006b868fc40cbfc97f30e53f46bbc41637cca [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.calcite.test;
import org.apache.calcite.adapter.enumerable.EnumerableRules;
import org.apache.calcite.plan.ConventionTraitDef;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.volcano.VolcanoPlanner;
import org.apache.calcite.rel.RelCollationTraitDef;
import org.apache.calcite.rel.rules.CoreRules;
import org.apache.calcite.rel.rules.JoinPushThroughJoinRule;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.junit.jupiter.api.Test;
import java.util.List;
/**
* Unit test for top-down optimization.
*
* <p>As input, the test supplies a SQL statement and rules; the SQL is
* translated into relational algebra and then fed into a
* {@link VolcanoPlanner}. The plan before and after "optimization" is
* diffed against a .ref file using {@link DiffRepository}.
*
* <p>Procedure for adding a new test case:
*
* <ol>
* <li>Add a new public test method for your rule, following the existing
* examples. You'll have to come up with an SQL statement to which your rule
* will apply in a meaningful way. See
* {@link org.apache.calcite.test.catalog.MockCatalogReaderSimple} class
* for details on the schema.
*
* <li>Run the test. It should fail. Inspect the output in
* {@code build/resources/test/.../TopDownOptTest_actual.xml}.
*
* <li>Verify that the "planBefore" is the correct
* translation of your SQL, and that it contains the pattern on which your rule
* is supposed to fire. If all is well, replace
* {@code src/test/resources/.../TopDownOptTest.xml} and
* with the new {@code build/resources/test/.../TopDownOptTest_actual.xml}.
*
* <li>Run the test again. It should fail again, but this time it should contain
* a "planAfter" entry for your rule. Verify that your rule applied its
* transformation correctly, and then update the
* {@code src/test/resources/.../TopDownOptTest.xml} file again.
*
* <li>Run the test one last time; this time it should pass.
* </ol>
*/
class TopDownOptTest extends RelOptTestBase {
@Test void testValuesTraitRequest() {
final String sql = "SELECT * from (values (1, 1), (2, 1), (1, 2), (2, 2))\n"
+ "as t(a, b) order by b, a";
Query.create(sql).check();
}
@Test void testValuesTraitRequestNeg() {
final String sql = "SELECT * from (values (1, 1), (2, 1), (3, 2), (2, 2))\n"
+ "as t(a, b) order by b, a";
Query.create(sql).check();
}
@Test void testSortAgg() {
final String sql = "select mgr, count(*) from sales.emp\n"
+ "group by mgr order by mgr desc nulls last limit 5";
Query.create(sql).check();
}
@Test void testSortAggPartialKey() {
final String sql = "select mgr,deptno,comm,count(*) from sales.emp\n"
+ "group by mgr,deptno,comm\n"
+ "order by comm desc nulls last, deptno nulls first";
Query.create(sql).check();
}
@Test void testSortMergeJoin() {
final String sql = "select * from\n"
+ "sales.emp r join sales.bonus s on r.ename=s.ename and r.job=s.job\n"
+ "order by r.job desc nulls last, r.ename nulls first";
Query.create(sql).check();
}
@Test void testSortMergeJoinSubsetKey() {
final String sql = "select * from\n"
+ "sales.emp r join sales.bonus s on r.ename=s.ename and r.job=s.job\n"
+ "order by r.job desc nulls last";
Query.create(sql).check();
}
@Test void testSortMergeJoinSubsetKey2() {
final String sql = "select * from\n"
+ "sales.emp r join sales.bonus s on r.ename=s.ename and r.job=s.job and r.sal = s.sal\n"
+ "order by r.sal, r.ename desc nulls last";
Query.create(sql).check();
}
@Test void testSortMergeJoinSupersetKey() {
final String sql = "select * from\n"
+ "sales.emp r join sales.bonus s on r.ename=s.ename and r.job=s.job\n"
+ "order by r.job desc nulls last, r.ename, r.sal desc";
Query.create(sql).check();
}
@Test void testSortMergeJoinRight() {
final String sql = "select * from\n"
+ "sales.emp r join sales.bonus s on r.ename=s.ename and r.job=s.job\n"
+ "order by s.job desc nulls last, s.ename nulls first";
Query.create(sql).check();
}
@Test void testSortMergeJoinRightSubsetKey() {
final String sql = "select * from\n"
+ "sales.emp r join sales.bonus s on r.ename=s.ename and r.job=s.job\n"
+ "order by s.job desc nulls last";
Query.create(sql).check();
}
@Test void testSortMergeJoinRightSubsetKey2() {
final String sql = "select * from\n"
+ "sales.emp r join sales.bonus s on r.ename=s.ename and r.job=s.job and r.sal = s.sal\n"
+ "order by s.sal, s.ename desc nulls last";
Query.create(sql).check();
}
@Test void testSortMergeJoinRightSupersetKey() {
final String sql = "select * from\n"
+ "sales.emp r join sales.bonus s on r.ename=s.ename and r.job=s.job\n"
+ "order by s.job desc nulls last, s.ename, s.sal desc";
Query.create(sql).check();
}
@Test void testMergeJoinDeriveLeft1() {
final String sql = "select * from\n"
+ "(select ename, job, max(sal) from sales.emp group by ename, job) r\n"
+ "join sales.bonus s on r.job=s.job and r.ename=s.ename";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.check();
}
@Test void testMergeJoinDeriveLeft2() {
final String sql = "select * from\n"
+ "(select ename, job, mgr, max(sal) from sales.emp group by ename, job, mgr) r\n"
+ "join sales.bonus s on r.job=s.job and r.ename=s.ename";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.check();
}
@Test void testMergeJoinDeriveRight1() {
final String sql = "select * from sales.bonus s join\n"
+ "(select ename, job, max(sal) from sales.emp group by ename, job) r\n"
+ "on r.job=s.job and r.ename=s.ename";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.check();
}
@Test void testMergeJoinDeriveRight2() {
final String sql = "select * from sales.bonus s join\n"
+ "(select ename, job, mgr, max(sal) from sales.emp group by ename, job, mgr) r\n"
+ "on r.job=s.job and r.ename=s.ename";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.check();
}
// Order by left field(s): push down sort to left input.
@Test void testCorrelateInnerJoinDeriveLeft() {
final String sql = "select * from emp e\n"
+ "join dept d on e.deptno=d.deptno\n"
+ "order by e.ename";
Query.create(sql)
.addRule(CoreRules.JOIN_TO_CORRELATE)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// Order by contains right field: sort cannot be pushed down.
@Test void testCorrelateInnerJoinNoDerive() {
final String sql = "select * from emp e\n"
+ "join dept d on e.deptno=d.deptno\n"
+ "order by e.ename, d.name";
Query.create(sql)
.addRule(CoreRules.JOIN_TO_CORRELATE)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// Order by left field(s): push down sort to left input.
@Test void testCorrelateLeftJoinDeriveLeft() {
final String sql = "select * from emp e\n"
+ "left join dept d on e.deptno=d.deptno\n"
+ "order by e.ename";
Query.create(sql)
.addRule(CoreRules.JOIN_TO_CORRELATE)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// Order by contains right field: sort cannot be pushed down.
@Test void testCorrelateLeftJoinNoDerive() {
final String sql = "select * from emp e\n"
+ "left join dept d on e.deptno=d.deptno\n"
+ "order by e.ename, d.name";
Query.create(sql)
.addRule(CoreRules.JOIN_TO_CORRELATE)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// Order by left field(s): push down sort to left input.
@Test void testCorrelateSemiJoinDeriveLeft() {
final String sql = "select * from dept d\n"
+ "where exists (select 1 from emp e where e.deptno=d.deptno)\n"
+ "order by d.name";
Query.create(sql)
.addRule(CoreRules.JOIN_TO_CORRELATE)
.addRule(CoreRules.JOIN_TO_SEMI_JOIN)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// test if "order by mgr desc nulls last" can be pushed through the projection ("select mgr").
@Test void testSortProject() {
final String sql = "select mgr from sales.emp order by mgr desc nulls last";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// test that Sort cannot push through projection because of non-trival call
// (e.g. RexCall(sal * -1)). In this example, the reason is that "sal * -1"
// creates opposite ordering if Sort is pushed down.
@Test void testSortProjectOnRexCall() {
final String sql = "select ename, sal * -1 as sal, mgr from\n"
+ "sales.emp order by ename desc, sal desc, mgr desc nulls last";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// test that Sort can push through projection when cast is monotonic.
@Test void testSortProjectWhenCastLeadingToMonotonic() {
final String sql = "select deptno from sales.emp order by cast(deptno as float) desc";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// test that Sort cannot push through projection when cast is not monotonic.
@Test void testSortProjectWhenCastLeadingToNonMonotonic() {
final String sql = "select deptno from sales.emp order by cast(deptno as varchar) desc";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// No sort on left join input.
@Test void testSortProjectDeriveWhenCastLeadingToMonotonic() {
final String sql = "select * from\n"
+ "(select ename, cast(job as varchar) as job, max_sal + 1 from\n"
+ "(select ename, job, max(sal) as max_sal from sales.emp group by ename, job) t) r\n"
+ "join sales.bonus s on r.job=s.job and r.ename=s.ename";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.check();
}
// need sort on left join input.
@Test void testSortProjectDeriveOnRexCall() {
final String sql = "select * from\n"
+ "(select ename, sal * -1 as sal, max_job from\n"
+ "(select ename, sal, max(job) as max_job from sales.emp group by ename, sal) t) r\n"
+ "join sales.bonus s on r.sal=s.sal and r.ename=s.ename";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.check();
}
// need sort on left join input.
@Test void testSortProjectDeriveWhenCastLeadingToNonMonotonic() {
final String sql = "select * from\n"
+ "(select ename, cast(job as numeric) as job, max_sal + 1 from\n"
+ "(select ename, job, max(sal) as max_sal from sales.emp group by ename, job) t) r\n"
+ "join sales.bonus s on r.job=s.job and r.ename=s.ename";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.check();
}
// no Sort need for left join input.
@Test void testSortProjectDerive3() {
final String sql = "select * from\n"
+ "(select ename, cast(job as varchar) as job, sal + 1 from\n"
+ "(select ename, job, sal from sales.emp limit 100) t) r\n"
+ "join sales.bonus s on r.job=s.job and r.ename=s.ename";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.check();
}
// need Sort on left join input.
@Test void testSortProjectDerive4() {
final String sql = "select * from\n"
+ "(select ename, cast(job as bigint) as job, sal + 1 from\n"
+ "(select ename, job, sal from sales.emp limit 100) t) r\n"
+ "join sales.bonus s on r.job=s.job and r.ename=s.ename";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.check();
}
// test if top projection can enforce sort when inner sort cannot produce satisfying ordering.
@Test void testSortProjectDerive5() {
final String sql = "select ename, empno*-1, job from\n"
+ "(select * from sales.emp order by ename, empno, job limit 10) order by ename, job";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
@Test void testSortProjectDerive() {
final String sql = "select * from\n"
+ "(select ename, job, max_sal + 1 from\n"
+ "(select ename, job, max(sal) as max_sal from sales.emp group by ename, job) t) r\n"
+ "join sales.bonus s on r.job=s.job and r.ename=s.ename";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.check();
}
// need Sort on projection.
@Test void testSortProjectDerive2() {
final String sql = "select distinct ename, sal*-2, mgr\n"
+ "from (select ename, mgr, sal from sales.emp order by ename, mgr, sal limit 100) t";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
@Test void testSortProjectDerive6() {
final String sql = "select comm, deptno, slacker from\n"
+ "(select * from sales.emp order by comm, deptno, slacker limit 10) t\n"
+ "order by comm, slacker";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// test traits push through filter.
@Test void testSortFilter() {
final String sql = "select ename, job, mgr, max_sal from\n"
+ "(select ename, job, mgr, max(sal) as max_sal from sales.emp group by ename, job, mgr) as t\n"
+ "where max_sal > 1000\n"
+ "order by mgr desc, ename";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// test traits derivation in filter.
@Test void testSortFilterDerive() {
final String sql = "select * from\n"
+ "(select ename, job, max_sal from\n"
+ "(select ename, job, max(sal) as max_sal from sales.emp group by ename, job) t where job > 1000) r\n"
+ "join sales.bonus s on r.job=s.job and r.ename=s.ename";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.check();
}
// Not push down sort for hash join in full outer join case.
@Test void testHashJoinFullOuterJoinNotPushDownSort() {
final String sql = "select * from\n"
+ "sales.emp r full outer join sales.bonus s on r.ename=s.ename and r.job=s.job\n"
+ "order by r.job desc nulls last, r.ename nulls first";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.check();
}
// Push down sort to left input.
@Test void testHashJoinLeftOuterJoinPushDownSort() {
final String sql = "select * from\n"
+ "(select contactno, email from customer.contact_peek) r left outer join\n"
+ "(select acctno, type from customer.account) s\n"
+ "on r.contactno=s.acctno and r.email=s.type\n"
+ "order by r.contactno desc, r.email desc";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// Push down sort to left input.
@Test void testHashJoinLeftOuterJoinPushDownSort2() {
final String sql = "select * from\n"
+ "customer.contact_peek r left outer join\n"
+ "customer.account s\n"
+ "on r.contactno=s.acctno and r.email=s.type\n"
+ "order by r.fname desc";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// Push down sort to left input.
@Test void testHashJoinInnerJoinPushDownSort() {
final String sql = "select * from\n"
+ "(select contactno, email from customer.contact_peek) r inner join\n"
+ "(select acctno, type from customer.account) s\n"
+ "on r.contactno=s.acctno and r.email=s.type\n"
+ "order by r.contactno desc, r.email desc";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// do not push down sort.
@Test void testHashJoinRightOuterJoinPushDownSort() {
final String sql = "select * from\n"
+ "(select contactno, email from customer.contact_peek) r right outer join\n"
+ "(select acctno, type from customer.account) s\n"
+ "on r.contactno=s.acctno and r.email=s.type\n"
+ "order by s.acctno desc, s.type desc";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// push sort to left input
@Test void testNestedLoopJoinLeftOuterJoinPushDownSort() {
final String sql = "select * from\n"
+ " customer.contact_peek r left outer join\n"
+ "customer.account s\n"
+ "on r.contactno>s.acctno and r.email<s.type\n"
+ "order by r.contactno desc, r.email desc";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// push sort to left input
@Test void testNestedLoopJoinLeftOuterJoinPushDownSort2() {
final String sql = "select * from\n"
+ " customer.contact_peek r left outer join\n"
+ "customer.account s\n"
+ "on r.contactno>s.acctno and r.email<s.type\n"
+ "order by r.fname desc";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// do not push sort to left input cause sort keys are on right input.
@Test void testNestedLoopJoinLeftOuterJoinSortKeyOnRightInput() {
final String sql = "select * from\n"
+ " customer.contact_peek r left outer join\n"
+ "customer.account s\n"
+ "on r.contactno>s.acctno and r.email<s.type\n"
+ "order by s.acctno desc, s.type desc";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// do not push down sort to right input because traits propagation does not work
// for right/full outer join.
@Test void testNestedLoopJoinRightOuterJoinSortPushDown() {
final String sql = "select r.contactno, r.email, s.acctno, s.type from\n"
+ " customer.contact_peek r right outer join\n"
+ "customer.account s\n"
+ "on r.contactno>s.acctno and r.email<s.type\n"
+ "order by s.acctno desc, s.type desc";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// Collation can be derived from left input so that top Sort is removed.
@Test void testHashJoinTraitDerivation() {
final String sql = "select * from\n"
+ "(select ename, job, mgr from sales.emp order by ename desc, job desc, mgr limit 10) r\n"
+ "join sales.bonus s on r.ename=s.ename and r.job=s.job\n"
+ "order by r.ename desc, r.job desc";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// Collation can be derived from left input so that top Sort is removed.
@Test void testHashJoinTraitDerivation2() {
final String sql = "select * from\n"
+ "(select ename, job, mgr from sales.emp order by mgr desc limit 10) r\n"
+ "join sales.bonus s on r.ename=s.ename and r.job=s.job\n"
+ "order by r.mgr desc";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// Collation derived from left input is not what the top Sort needs.
@Test void testHashJoinTraitDerivationNegativeCase() {
final String sql = "select * from\n"
+ "(select ename, job, mgr from sales.emp order by mgr desc limit 10) r\n"
+ "join sales.bonus s on r.ename=s.ename and r.job=s.job\n"
+ "order by r.mgr";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// Collation can be derived from left input so that top Sort is removed.
@Test void testNestedLoopJoinTraitDerivation() {
final String sql = "select * from\n"
+ "(select ename, job, mgr from sales.emp order by ename desc, job desc, mgr limit 10) r\n"
+ "join sales.bonus s on r.ename>s.ename and r.job<s.job\n"
+ "order by r.ename desc, r.job desc";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// Collation can be derived from left input so that top Sort is removed.
@Test void testNestedLoopJoinTraitDerivation2() {
final String sql = "select * from\n"
+ "(select ename, job, mgr from sales.emp order by mgr limit 10) r\n"
+ "join sales.bonus s on r.ename>s.ename and r.job<s.job\n"
+ "order by r.mgr";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// Collation derived from left input is not what the top Sort needs.
@Test void testNestedLoopJoinTraitDerivationNegativeCase() {
final String sql = "select * from\n"
+ "(select ename, job, mgr from sales.emp order by mgr limit 10) r\n"
+ "join sales.bonus s on r.ename>s.ename and r.job<s.job\n"
+ "order by r.mgr desc";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.check();
}
// test if "order by mgr desc nulls last" can be pushed through the calc ("select mgr").
@Test void testSortCalc() {
final String sql = "select mgr from sales.emp order by mgr desc nulls last";
Query.create(sql)
.addRule(CoreRules.PROJECT_TO_CALC)
.addRule(EnumerableRules.ENUMERABLE_CALC_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.removeRule(EnumerableRules.ENUMERABLE_PROJECT_RULE)
.check();
}
// test that Sort cannot push through calc because of non-trival call
// (e.g. RexCall(sal * -1)). In this example, the reason is that "sal * -1"
// creates opposite ordering if Sort is pushed down.
@Test void testSortCalcOnRexCall() {
final String sql = "select ename, sal * -1 as sal, mgr from\n"
+ "sales.emp order by ename desc, sal desc, mgr desc nulls last";
Query.create(sql)
.addRule(CoreRules.PROJECT_TO_CALC)
.addRule(EnumerableRules.ENUMERABLE_CALC_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.removeRule(EnumerableRules.ENUMERABLE_PROJECT_RULE)
.check();
}
// test that Sort can push through calc when cast is monotonic.
@Test void testSortCalcWhenCastLeadingToMonotonic() {
final String sql = "select cast(deptno as float) from sales.emp order by deptno desc";
Query.create(sql)
.addRule(CoreRules.PROJECT_TO_CALC)
.addRule(EnumerableRules.ENUMERABLE_CALC_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.removeRule(EnumerableRules.ENUMERABLE_PROJECT_RULE)
.check();
}
// test that Sort cannot push through calc when cast is not monotonic.
@Test void testSortCalcWhenCastLeadingToNonMonotonic() {
final String sql = "select deptno from sales.emp order by cast(deptno as varchar) desc";
Query.create(sql)
.addRule(CoreRules.PROJECT_TO_CALC)
.addRule(EnumerableRules.ENUMERABLE_CALC_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.removeRule(EnumerableRules.ENUMERABLE_PROJECT_RULE)
.check();
}
// test traits push through calc with filter.
@Test void testSortCalcWithFilter() {
final String sql = "select ename, job, mgr, max_sal from\n"
+ "(select ename, job, mgr, max(sal) as max_sal from sales.emp group by ename, job, mgr) as t\n"
+ "where max_sal > 1000\n"
+ "order by mgr desc, ename";
Query.create(sql)
.addRule(CoreRules.PROJECT_TO_CALC)
.addRule(CoreRules.FILTER_TO_CALC)
.addRule(EnumerableRules.ENUMERABLE_CALC_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.removeRule(EnumerableRules.ENUMERABLE_PROJECT_RULE)
.removeRule(EnumerableRules.ENUMERABLE_FILTER_RULE)
.check();
}
// Do not need Sort for calc.
@Test void testSortCalcDerive1() {
final String sql = "select * from\n"
+ "(select ename, job, max_sal + 1 from\n"
+ "(select ename, job, max(sal) as max_sal from sales.emp "
+ "group by ename, job) t) r\n"
+ "join sales.bonus s on r.job=s.job and r.ename=s.ename";
Query.create(sql)
.addRule(CoreRules.PROJECT_TO_CALC)
.addRule(EnumerableRules.ENUMERABLE_CALC_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.removeRule(EnumerableRules.ENUMERABLE_PROJECT_RULE)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.check();
}
// Need Sort for calc.
@Test void testSortCalcDerive2() {
final String sql = "select distinct ename, sal*-2, mgr\n"
+ "from (select ename, mgr, sal from sales.emp order by ename, mgr, sal limit 100) t";
Query.create(sql)
.addRule(CoreRules.PROJECT_TO_CALC)
.addRule(EnumerableRules.ENUMERABLE_CALC_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.removeRule(EnumerableRules.ENUMERABLE_PROJECT_RULE)
.check();
}
// Do not need Sort for left join input.
@Test void testSortCalcDerive3() {
final String sql = "select * from\n"
+ "(select ename, cast(job as varchar) as job, sal + 1 from\n"
+ "(select ename, job, sal from sales.emp limit 100) t) r\n"
+ "join sales.bonus s on r.job=s.job and r.ename=s.ename";
Query.create(sql)
.addRule(CoreRules.PROJECT_TO_CALC)
.addRule(EnumerableRules.ENUMERABLE_CALC_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.removeRule(EnumerableRules.ENUMERABLE_PROJECT_RULE)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.check();
}
// push sort to left input
@Test void testBatchNestedLoopJoinLeftOuterJoinPushDownSort() {
final String sql = "select * from\n"
+ " customer.contact_peek r left outer join\n"
+ "customer.account s\n"
+ "on r.contactno>s.acctno and r.email<s.type\n"
+ "order by r.contactno desc, r.email desc";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.addRule(EnumerableRules.ENUMERABLE_BATCH_NESTED_LOOP_JOIN_RULE)
.check();
}
// Collation can be derived from left input so that top Sort is removed.
@Test void testBatchNestedLoopJoinTraitDerivation() {
final String sql = "select * from\n"
+ "(select ename, job, mgr from sales.emp order by ename desc, job desc, mgr limit 10) r\n"
+ "join sales.bonus s on r.ename>s.ename and r.job<s.job\n"
+ "order by r.ename desc, r.job desc";
Query.create(sql)
.removeRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_JOIN_RULE)
.removeRule(EnumerableRules.ENUMERABLE_SORT_RULE)
.addRule(EnumerableRules.ENUMERABLE_BATCH_NESTED_LOOP_JOIN_RULE)
.check();
}
}
/**
* A helper class that creates Volcano planner with top-down optimization enabled. This class
* allows easy-to-add and easy-to-remove rules from the planner.
*/
class Query extends RelOptTestBase {
protected DiffRepository getDiffRepos() {
return DiffRepository.lookup(TopDownOptTest.class);
}
private String sql;
private VolcanoPlanner planner;
private Query(String sql) {
this.sql = sql;
planner = new VolcanoPlanner();
// Always use top-down optimization
planner.setTopDownOpt(true);
planner.addRelTraitDef(ConventionTraitDef.INSTANCE);
planner.addRelTraitDef(RelCollationTraitDef.INSTANCE);
RelOptUtil.registerDefaultRules(planner, false, false);
// Remove to Keep deterministic join order.
planner.removeRule(CoreRules.JOIN_COMMUTE);
planner.removeRule(JoinPushThroughJoinRule.LEFT);
planner.removeRule(JoinPushThroughJoinRule.RIGHT);
// Always use sorted agg.
planner.addRule(EnumerableRules.ENUMERABLE_SORTED_AGGREGATE_RULE);
planner.removeRule(EnumerableRules.ENUMERABLE_AGGREGATE_RULE);
// pushing down sort should be handled by top-down optimization.
planner.removeRule(CoreRules.SORT_PROJECT_TRANSPOSE);
// Sort will only be pushed down by traits propagation.
planner.removeRule(CoreRules.SORT_JOIN_TRANSPOSE);
planner.removeRule(CoreRules.SORT_JOIN_COPY);
}
public static Query create(String sql) {
return new Query(sql);
}
public Query addRule(RelOptRule ruleToAdd) {
planner.addRule(ruleToAdd);
return this;
}
public Query addRules(List<RelOptRule> rulesToAdd) {
for (RelOptRule ruleToAdd : rulesToAdd) {
planner.addRule(ruleToAdd);
}
return this;
}
public Query removeRule(RelOptRule ruleToRemove) {
planner.removeRule(ruleToRemove);
return this;
}
public Query removeRules(List<RelOptRule> rulesToRemove) {
for (RelOptRule ruleToRemove : rulesToRemove) {
planner.removeRule(ruleToRemove);
}
return this;
}
public void check() {
SqlToRelTestBase.Tester tester = createTester().withDecorrelation(true)
.withClusterFactory(cluster -> RelOptCluster.create(planner, cluster.getRexBuilder()));
final Sql sql =
new Sql(tester, this.sql, null, planner, ImmutableMap.of(),
ImmutableList.of(), null);
sql.check();
}
}