blob: 4ff890227e84b8acacfaa529c91b1ce47e111c60 [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.EnumerableConvention;
import org.apache.calcite.adapter.enumerable.EnumerableRules;
import org.apache.calcite.config.CalciteConnectionConfigImpl;
import org.apache.calcite.plan.Context;
import org.apache.calcite.plan.Contexts;
import org.apache.calcite.plan.ConventionTraitDef;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptPlanner;
import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.RelTraitDef;
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.plan.hep.HepMatchOrder;
import org.apache.calcite.plan.hep.HepPlanner;
import org.apache.calcite.plan.hep.HepProgram;
import org.apache.calcite.plan.hep.HepProgramBuilder;
import org.apache.calcite.plan.volcano.VolcanoPlanner;
import org.apache.calcite.prepare.Prepare;
import org.apache.calcite.rel.RelCollation;
import org.apache.calcite.rel.RelCollationTraitDef;
import org.apache.calcite.rel.RelCollations;
import org.apache.calcite.rel.RelDistributionTraitDef;
import org.apache.calcite.rel.RelDistributions;
import org.apache.calcite.rel.RelFieldCollation;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.RelRoot;
import org.apache.calcite.rel.core.Aggregate;
import org.apache.calcite.rel.core.CorrelationId;
import org.apache.calcite.rel.core.Filter;
import org.apache.calcite.rel.core.Intersect;
import org.apache.calcite.rel.core.Join;
import org.apache.calcite.rel.core.JoinRelType;
import org.apache.calcite.rel.core.Minus;
import org.apache.calcite.rel.core.Project;
import org.apache.calcite.rel.core.RelFactories;
import org.apache.calcite.rel.core.Union;
import org.apache.calcite.rel.logical.LogicalAggregate;
import org.apache.calcite.rel.logical.LogicalCorrelate;
import org.apache.calcite.rel.logical.LogicalFilter;
import org.apache.calcite.rel.logical.LogicalProject;
import org.apache.calcite.rel.logical.LogicalTableModify;
import org.apache.calcite.rel.logical.LogicalTableScan;
import org.apache.calcite.rel.rules.AggregateCaseToFilterRule;
import org.apache.calcite.rel.rules.AggregateExpandDistinctAggregatesRule;
import org.apache.calcite.rel.rules.AggregateExtractProjectRule;
import org.apache.calcite.rel.rules.AggregateFilterTransposeRule;
import org.apache.calcite.rel.rules.AggregateJoinJoinRemoveRule;
import org.apache.calcite.rel.rules.AggregateJoinRemoveRule;
import org.apache.calcite.rel.rules.AggregateJoinTransposeRule;
import org.apache.calcite.rel.rules.AggregateMergeRule;
import org.apache.calcite.rel.rules.AggregateProjectMergeRule;
import org.apache.calcite.rel.rules.AggregateProjectPullUpConstantsRule;
import org.apache.calcite.rel.rules.AggregateReduceFunctionsRule;
import org.apache.calcite.rel.rules.AggregateRemoveRule;
import org.apache.calcite.rel.rules.AggregateUnionAggregateRule;
import org.apache.calcite.rel.rules.AggregateUnionTransposeRule;
import org.apache.calcite.rel.rules.AggregateValuesRule;
import org.apache.calcite.rel.rules.CalcMergeRule;
import org.apache.calcite.rel.rules.CoerceInputsRule;
import org.apache.calcite.rel.rules.DateRangeRules;
import org.apache.calcite.rel.rules.ExchangeRemoveConstantKeysRule;
import org.apache.calcite.rel.rules.FilterAggregateTransposeRule;
import org.apache.calcite.rel.rules.FilterJoinRule;
import org.apache.calcite.rel.rules.FilterMergeRule;
import org.apache.calcite.rel.rules.FilterMultiJoinMergeRule;
import org.apache.calcite.rel.rules.FilterProjectTransposeRule;
import org.apache.calcite.rel.rules.FilterRemoveIsNotDistinctFromRule;
import org.apache.calcite.rel.rules.FilterSetOpTransposeRule;
import org.apache.calcite.rel.rules.FilterToCalcRule;
import org.apache.calcite.rel.rules.IntersectToDistinctRule;
import org.apache.calcite.rel.rules.JoinAddRedundantSemiJoinRule;
import org.apache.calcite.rel.rules.JoinCommuteRule;
import org.apache.calcite.rel.rules.JoinExtractFilterRule;
import org.apache.calcite.rel.rules.JoinProjectTransposeRule;
import org.apache.calcite.rel.rules.JoinPushExpressionsRule;
import org.apache.calcite.rel.rules.JoinPushTransitivePredicatesRule;
import org.apache.calcite.rel.rules.JoinToMultiJoinRule;
import org.apache.calcite.rel.rules.JoinUnionTransposeRule;
import org.apache.calcite.rel.rules.ProjectCorrelateTransposeRule;
import org.apache.calcite.rel.rules.ProjectFilterTransposeRule;
import org.apache.calcite.rel.rules.ProjectJoinJoinRemoveRule;
import org.apache.calcite.rel.rules.ProjectJoinRemoveRule;
import org.apache.calcite.rel.rules.ProjectJoinTransposeRule;
import org.apache.calcite.rel.rules.ProjectMergeRule;
import org.apache.calcite.rel.rules.ProjectMultiJoinMergeRule;
import org.apache.calcite.rel.rules.ProjectRemoveRule;
import org.apache.calcite.rel.rules.ProjectSetOpTransposeRule;
import org.apache.calcite.rel.rules.ProjectToCalcRule;
import org.apache.calcite.rel.rules.ProjectToWindowRule;
import org.apache.calcite.rel.rules.ProjectWindowTransposeRule;
import org.apache.calcite.rel.rules.PruneEmptyRules;
import org.apache.calcite.rel.rules.PushProjector;
import org.apache.calcite.rel.rules.ReduceDecimalsRule;
import org.apache.calcite.rel.rules.ReduceExpressionsRule;
import org.apache.calcite.rel.rules.SemiJoinFilterTransposeRule;
import org.apache.calcite.rel.rules.SemiJoinJoinTransposeRule;
import org.apache.calcite.rel.rules.SemiJoinProjectTransposeRule;
import org.apache.calcite.rel.rules.SemiJoinRemoveRule;
import org.apache.calcite.rel.rules.SemiJoinRule;
import org.apache.calcite.rel.rules.SortJoinCopyRule;
import org.apache.calcite.rel.rules.SortJoinTransposeRule;
import org.apache.calcite.rel.rules.SortProjectTransposeRule;
import org.apache.calcite.rel.rules.SortRemoveConstantKeysRule;
import org.apache.calcite.rel.rules.SortUnionTransposeRule;
import org.apache.calcite.rel.rules.SubQueryRemoveRule;
import org.apache.calcite.rel.rules.UnionMergeRule;
import org.apache.calcite.rel.rules.UnionPullUpConstantsRule;
import org.apache.calcite.rel.rules.UnionToDistinctRule;
import org.apache.calcite.rel.rules.ValuesReduceRule;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexOver;
import org.apache.calcite.runtime.Hook;
import org.apache.calcite.sql.SqlFunction;
import org.apache.calcite.sql.SqlFunctionCategory;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlOperatorBinding;
import org.apache.calcite.sql.SqlSpecialOperator;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.type.ReturnTypes;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.validate.SqlMonotonicity;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql2rel.RelDecorrelator;
import org.apache.calcite.sql2rel.SqlToRelConverter;
import org.apache.calcite.test.catalog.MockCatalogReader;
import org.apache.calcite.tools.FrameworkConfig;
import org.apache.calcite.tools.Frameworks;
import org.apache.calcite.tools.Program;
import org.apache.calcite.tools.Programs;
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.tools.RelBuilderFactory;
import org.apache.calcite.tools.RuleSet;
import org.apache.calcite.tools.RuleSets;
import org.apache.calcite.util.ImmutableBitSet;
import com.google.common.collect.ImmutableList;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.function.Predicate;
import static org.apache.calcite.plan.RelOptRule.none;
import static org.apache.calcite.plan.RelOptRule.operand;
import static org.apache.calcite.plan.RelOptRule.operandJ;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* Unit test for rules in {@code org.apache.calcite.rel} and subpackages.
*
* <p>As input, the test supplies a SQL statement and a single rule; the SQL is
* translated into relational algebra and then fed into a
* {@link org.apache.calcite.plan.hep.HepPlanner}. The planner fires the rule on
* every
* pattern match in a depth-first left-to-right pre-order traversal of the tree
* for as long as the rule continues to succeed in applying its transform. (For
* rules which call transformTo more than once, only the last result is used.)
* 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 SqlToRelTestBase} class comments
* for details on the schema.
*
* <li>Run the test. It should fail. Inspect the output in
* {@code target/surefire/.../RelOptRulesTest.xml}.
* (If you are running using maven and this file does not exist, add a
* {@code -X} flag to the maven command line.)
*
* <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/.../RelOptRulesTest.xml} and
* with the new {@code target/surefire/.../RelOptRulesTest.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/.../RelOptRulesTest.xml} file again.
*
* <li>Run the test one last time; this time it should pass.
* </ol>
*/
class RelOptRulesTest extends RelOptTestBase {
//~ Methods ----------------------------------------------------------------
private final PushProjector.ExprCondition skipItem = expr ->
expr instanceof RexCall
&& "item".equalsIgnoreCase(((RexCall) expr).getOperator().getName());
protected DiffRepository getDiffRepos() {
return DiffRepository.lookup(RelOptRulesTest.class);
}
@Test void testReduceNot() {
HepProgram preProgram = new HepProgramBuilder()
.build();
HepProgramBuilder builder = new HepProgramBuilder();
builder.addRuleClass(ReduceExpressionsRule.class);
HepPlanner hepPlanner = new HepPlanner(builder.build());
hepPlanner.addRule(ReduceExpressionsRule.FILTER_INSTANCE);
final String sql = "select *\n"
+ "from (select (case when sal > 1000 then null else false end) as caseCol from emp)\n"
+ "where NOT(caseCol)";
sql(sql).withPre(preProgram)
.with(hepPlanner)
.checkUnchanged();
}
@Test void testReduceNestedCaseWhen() {
HepProgram preProgram = new HepProgramBuilder()
.build();
HepProgramBuilder builder = new HepProgramBuilder();
builder.addRuleClass(ReduceExpressionsRule.class);
HepPlanner hepPlanner = new HepPlanner(builder.build());
hepPlanner.addRule(ReduceExpressionsRule.FILTER_INSTANCE);
final String sql = "select sal\n"
+ "from emp\n"
+ "where case when (sal = 1000) then\n"
+ "(case when sal = 1000 then null else 1 end is null) else\n"
+ "(case when sal = 2000 then null else 1 end is null) end is true";
sql(sql).withPre(preProgram)
.with(hepPlanner)
.check();
}
@Test void testDigestOfApproximateDistinctAggregateCall() {
HepProgram preProgram = new HepProgramBuilder()
.build();
HepProgramBuilder builder = new HepProgramBuilder();
builder.addRuleClass(AggregateProjectMergeRule.class);
HepPlanner hepPlanner = new HepPlanner(builder.build());
hepPlanner.addRule(AggregateProjectMergeRule.INSTANCE);
final String sql = "select *\n"
+ "from (\n"
+ "select deptno, count(distinct empno) from emp group by deptno\n"
+ "union all\n"
+ "select deptno, approx_count_distinct(empno) from emp group by deptno)";
sql(sql).withPre(preProgram)
.with(hepPlanner)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1479">[CALCITE-1479]
* AssertionError in ReduceExpressionsRule on multi-column IN
* sub-query</a>. */
@Test void testReduceCompositeInSubQuery() {
final HepProgram hepProgram = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.build();
final String sql = "select *\n"
+ "from emp\n"
+ "where (empno, deptno) in (\n"
+ " select empno, deptno from (\n"
+ " select empno, deptno\n"
+ " from emp\n"
+ " group by empno, deptno))\n"
+ "or deptno < 40 + 60";
checkSubQuery(sql)
.with(hepProgram)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2865">[CALCITE-2865]
* FilterProjectTransposeRule generates wrong traitSet when copyFilter/Project is true</a>. */
@Test void testFilterProjectTransposeRule() {
List<RelOptRule> rules = Arrays.asList(
FilterProjectTransposeRule.INSTANCE, // default: copyFilter=true, copyProject=true
new FilterProjectTransposeRule(Filter.class, Project.class,
false, false, RelFactories.LOGICAL_BUILDER));
for (RelOptRule rule : rules) {
RelBuilder b = RelBuilder.create(RelBuilderTest.config().build());
RelNode in = b
.scan("EMP")
.sort(-4) // salary desc
.project(b.field(3)) // salary
.filter(b.equals(b.field(0), b.literal(11500))) // salary = 11500
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(rule)
.build();
HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(in);
RelNode result = hepPlanner.findBestExp();
// Verify LogicalFilter traitSet (must be [3 DESC])
RelNode filter = result.getInput(0);
RelCollation collation = filter.getTraitSet().getTrait(RelCollationTraitDef.INSTANCE);
assertNotNull(collation);
List<RelFieldCollation> fieldCollations = collation.getFieldCollations();
assertEquals(1, fieldCollations.size());
RelFieldCollation fieldCollation = fieldCollations.get(0);
assertEquals(3, fieldCollation.getFieldIndex());
assertEquals(RelFieldCollation.Direction.DESCENDING, fieldCollation.getDirection());
}
}
@Test void testReduceOrCaseWhen() {
HepProgram preProgram = new HepProgramBuilder()
.build();
HepProgramBuilder builder = new HepProgramBuilder();
builder.addRuleClass(ReduceExpressionsRule.class);
HepPlanner hepPlanner = new HepPlanner(builder.build());
hepPlanner.addRule(ReduceExpressionsRule.FILTER_INSTANCE);
final String sql = "select sal\n"
+ "from emp\n"
+ "where case when sal = 1000 then null else 1 end is null\n"
+ "OR case when sal = 2000 then null else 1 end is null";
sql(sql).withPre(preProgram)
.with(hepPlanner)
.check();
}
@Test void testReduceNullableCase() {
HepProgramBuilder builder = new HepProgramBuilder();
builder.addRuleClass(ReduceExpressionsRule.class);
HepPlanner hepPlanner = new HepPlanner(builder.build());
hepPlanner.addRule(ReduceExpressionsRule.PROJECT_INSTANCE);
final String sql = "SELECT CASE WHEN 1=2 "
+ "THEN cast((values(1)) as integer) "
+ "ELSE 2 end from (values(1))";
sql(sql).with(hepPlanner).checkUnchanged();
}
@Test void testReduceNullableCase2() {
HepProgramBuilder builder = new HepProgramBuilder();
builder.addRuleClass(ReduceExpressionsRule.class);
HepPlanner hepPlanner = new HepPlanner(builder.build());
hepPlanner.addRule(ReduceExpressionsRule.PROJECT_INSTANCE);
final String sql = "SELECT deptno, ename, CASE WHEN 1=2 "
+ "THEN substring(ename, 1, cast(2 as int)) ELSE NULL end from emp"
+ " group by deptno, ename, case when 1=2 then substring(ename,1, cast(2 as int)) else null end";
sql(sql).with(hepPlanner).checkUnchanged();
}
@Test void testProjectToWindowRuleForMultipleWindows() {
HepProgram preProgram = new HepProgramBuilder()
.build();
HepProgramBuilder builder = new HepProgramBuilder();
builder.addRuleClass(ProjectToWindowRule.class);
HepPlanner hepPlanner = new HepPlanner(builder.build());
hepPlanner.addRule(ProjectToWindowRule.PROJECT);
final String sql = "select\n"
+ " count(*) over(partition by empno order by sal) as count1,\n"
+ " count(*) over(partition by deptno order by sal) as count2,\n"
+ " sum(deptno) over(partition by empno order by sal) as sum1,\n"
+ " sum(deptno) over(partition by deptno order by sal) as sum2\n"
+ "from emp";
sql(sql).withPre(preProgram)
.with(hepPlanner)
.check();
}
@Test void testUnionToDistinctRule() {
final String sql = "select * from dept union select * from dept";
sql(sql).withRule(UnionToDistinctRule.INSTANCE).check();
}
@Test void testExtractJoinFilterRule() {
final String sql = "select 1 from emp inner join dept on emp.deptno=dept.deptno";
sql(sql).withRule(JoinExtractFilterRule.INSTANCE).check();
}
@Test void testNotPushExpression() {
final String sql = "select 1 from emp inner join dept\n"
+ "on emp.deptno=dept.deptno and emp.ename is not null";
sql(sql).withRule(JoinPushExpressionsRule.INSTANCE)
.checkUnchanged();
}
@Test void testAddRedundantSemiJoinRule() {
final String sql = "select 1 from emp inner join dept on emp.deptno = dept.deptno";
sql(sql).withRule(JoinAddRedundantSemiJoinRule.INSTANCE).check();
}
@Test void testStrengthenJoinType() {
// The "Filter(... , right.c IS NOT NULL)" above a left join is pushed into
// the join, makes it an inner join, and then disappears because c is NOT
// NULL.
final HepProgram preProgram =
HepProgram.builder()
.addRuleInstance(ProjectMergeRule.INSTANCE)
.addRuleInstance(FilterProjectTransposeRule.INSTANCE)
.build();
final HepProgram program =
HepProgram.builder()
.addRuleInstance(FilterJoinRule.FILTER_ON_JOIN)
.build();
final String sql = "select *\n"
+ "from dept left join emp on dept.deptno = emp.deptno\n"
+ "where emp.deptno is not null and emp.sal > 100";
sql(sql)
.withDecorrelation(true)
.withTrim(true)
.withPre(preProgram)
.with(program)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3170">[CALCITE-3170]
* ANTI join on conditions push down generates wrong plan</a>. */
@Test void testCanNotPushAntiJoinConditionsToLeft() {
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
// build a rel equivalent to sql:
// select * from emp
// where emp.deptno
// not in (select dept.deptno from dept where emp.deptno > 20)
RelNode left = relBuilder.scan("EMP").build();
RelNode right = relBuilder.scan("DEPT").build();
RelNode relNode = relBuilder.push(left)
.push(right)
.antiJoin(
relBuilder.call(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
relBuilder.field(2, 0, "DEPTNO"),
relBuilder.field(2, 1, "DEPTNO")),
relBuilder.call(SqlStdOperatorTable.GREATER_THAN,
RexInputRef.of(0, left.getRowType()),
relBuilder.literal(20)))
.project(relBuilder.field(0))
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(FilterJoinRule.JOIN)
.build();
HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
RelNode output = hepPlanner.findBestExp();
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
SqlToRelTestBase.assertValid(output);
}
@Test void testCanNotPushAntiJoinConditionsToRight() {
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
// build a rel equivalent to sql:
// select * from emp
// where emp.deptno
// not in (select dept.deptno from dept where dept.dname = 'ddd')
RelNode relNode = relBuilder.scan("EMP")
.scan("DEPT")
.antiJoin(
relBuilder.call(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
relBuilder.field(2, 0, "DEPTNO"),
relBuilder.field(2, 1, "DEPTNO")),
relBuilder.equals(relBuilder.field(2, 1, "DNAME"),
relBuilder.literal("ddd")))
.project(relBuilder.field(0))
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(FilterJoinRule.JOIN)
.build();
HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
RelNode output = hepPlanner.findBestExp();
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
SqlToRelTestBase.assertValid(output);
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3171">[CALCITE-3171]
* SemiJoin on conditions push down throws IndexOutOfBoundsException</a>. */
@Test void testPushSemiJoinConditionsToLeft() {
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
// build a rel equivalent to sql:
// select * from emp
// where emp.deptno
// in (select dept.deptno from dept where emp.empno > 20)
RelNode left = relBuilder.scan("EMP").build();
RelNode right = relBuilder.scan("DEPT").build();
RelNode relNode = relBuilder.push(left)
.push(right)
.semiJoin(
relBuilder.call(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
relBuilder.field(2, 0, "DEPTNO"),
relBuilder.field(2, 1, "DEPTNO")),
relBuilder.call(SqlStdOperatorTable.GREATER_THAN,
RexInputRef.of(0, left.getRowType()),
relBuilder.literal(20)))
.project(relBuilder.field(0))
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(JoinPushExpressionsRule.INSTANCE)
.build();
HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
RelNode output = hepPlanner.findBestExp();
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
SqlToRelTestBase.assertValid(output);
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3887">[CALCITE-3887]
* Filter and Join conditions may not need to retain nullability during simplifications</a>. */
@Test void testPushSemiJoinConditions() {
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
RelNode left = relBuilder.scan("EMP")
.project(
relBuilder.field("DEPTNO"),
relBuilder.field("ENAME"))
.build();
RelNode right = relBuilder.scan("DEPT")
.project(
relBuilder.field("DEPTNO"),
relBuilder.field("DNAME"))
.build();
relBuilder.push(left).push(right);
RexInputRef ref1 = relBuilder.field(2, 0, "DEPTNO");
RexInputRef ref2 = relBuilder.field(2, 1, "DEPTNO");
RexInputRef ref3 = relBuilder.field(2, 0, "ENAME");
RexInputRef ref4 = relBuilder.field(2, 1, "DNAME");
// ref1 IS NOT DISTINCT FROM ref2
RexCall cond1 = (RexCall) relBuilder.call(
SqlStdOperatorTable.OR,
relBuilder.call(SqlStdOperatorTable.EQUALS, ref1, ref2),
relBuilder.call(SqlStdOperatorTable.AND,
relBuilder.call(SqlStdOperatorTable.IS_NULL, ref1),
relBuilder.call(SqlStdOperatorTable.IS_NULL, ref2)));
// ref3 IS NOT DISTINCT FROM ref4
RexCall cond2 = (RexCall) relBuilder.call(
SqlStdOperatorTable.OR,
relBuilder.call(SqlStdOperatorTable.EQUALS, ref3, ref4),
relBuilder.call(SqlStdOperatorTable.AND,
relBuilder.call(SqlStdOperatorTable.IS_NULL, ref3),
relBuilder.call(SqlStdOperatorTable.IS_NULL, ref4)));
RexNode cond = relBuilder.call(SqlStdOperatorTable.AND, cond1, cond2);
RelNode relNode = relBuilder.semiJoin(cond)
.project(relBuilder.field(0))
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(JoinPushExpressionsRule.INSTANCE)
.addRuleInstance(SemiJoinProjectTransposeRule.INSTANCE)
.addRuleInstance(ReduceExpressionsRule.JOIN_INSTANCE)
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.build();
HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
RelNode output = hepPlanner.findBestExp();
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
SqlToRelTestBase.assertValid(output);
}
@Test void testFullOuterJoinSimplificationToLeftOuter() {
final String sql = "select 1 from sales.dept d full outer join sales.emp e\n"
+ "on d.deptno = e.deptno\n"
+ "where d.name = 'Charlie'";
sql(sql).withRule(FilterJoinRule.FILTER_ON_JOIN).check();
}
@Test void testFullOuterJoinSimplificationToRightOuter() {
final String sql = "select 1 from sales.dept d full outer join sales.emp e\n"
+ "on d.deptno = e.deptno\n"
+ "where e.sal > 100";
sql(sql).withRule(FilterJoinRule.FILTER_ON_JOIN).check();
}
@Test void testFullOuterJoinSimplificationToInner() {
final String sql = "select 1 from sales.dept d full outer join sales.emp e\n"
+ "on d.deptno = e.deptno\n"
+ "where d.name = 'Charlie' and e.sal > 100";
sql(sql).withRule(FilterJoinRule.FILTER_ON_JOIN).check();
}
@Test void testLeftOuterJoinSimplificationToInner() {
final String sql = "select 1 from sales.dept d left outer join sales.emp e\n"
+ "on d.deptno = e.deptno\n"
+ "where e.sal > 100";
sql(sql).withRule(FilterJoinRule.FILTER_ON_JOIN).check();
}
@Test void testRightOuterJoinSimplificationToInner() {
final String sql = "select 1 from sales.dept d right outer join sales.emp e\n"
+ "on d.deptno = e.deptno\n"
+ "where d.name = 'Charlie'";
sql(sql).withRule(FilterJoinRule.FILTER_ON_JOIN).check();
}
@Test void testPushAboveFiltersIntoInnerJoinCondition() {
final String sql = ""
+ "select * from sales.dept d inner join sales.emp e\n"
+ "on d.deptno = e.deptno and d.deptno > e.mgr\n"
+ "where d.deptno > e.mgr";
sql(sql).withRule(FilterJoinRule.FILTER_ON_JOIN).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3225">[CALCITE-3225]
* JoinToMultiJoinRule should not match SEMI/ANTI LogicalJoin</a>. */
@Test void testJoinToMultiJoinDoesNotMatchSemiJoin() {
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
// build a rel equivalent to sql:
// select * from
// (select * from emp join dept ON emp.deptno = emp.deptno) t
// where emp.job in (select job from bonus)
RelNode left = relBuilder.scan("EMP").build();
RelNode right = relBuilder.scan("DEPT").build();
RelNode semiRight = relBuilder.scan("BONUS").build();
RelNode relNode = relBuilder.push(left)
.push(right)
.join(
JoinRelType.INNER,
relBuilder.call(SqlStdOperatorTable.EQUALS,
relBuilder.field(2, 0, "DEPTNO"),
relBuilder.field(2, 1, "DEPTNO")))
.push(semiRight)
.semiJoin(
relBuilder.call(SqlStdOperatorTable.EQUALS,
relBuilder.field(2, 0, "JOB"),
relBuilder.field(2, 1, "JOB")))
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(JoinToMultiJoinRule.INSTANCE)
.build();
HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
RelNode output = hepPlanner.findBestExp();
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
SqlToRelTestBase.assertValid(output);
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3225">[CALCITE-3225]
* JoinToMultiJoinRule should not match SEMI/ANTI LogicalJoin</a>. */
@Test void testJoinToMultiJoinDoesNotMatchAntiJoin() {
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
// build a rel equivalent to sql:
// select * from
// (select * from emp join dept ON emp.deptno = emp.deptno) t
// where not exists (select job from bonus where emp.job = bonus.job)
RelNode left = relBuilder.scan("EMP").build();
RelNode right = relBuilder.scan("DEPT").build();
RelNode antiRight = relBuilder.scan("BONUS").build();
RelNode relNode = relBuilder.push(left)
.push(right)
.join(
JoinRelType.INNER,
relBuilder.call(SqlStdOperatorTable.EQUALS,
relBuilder.field(2, 0, "DEPTNO"),
relBuilder.field(2, 1, "DEPTNO")))
.push(antiRight)
.antiJoin(
relBuilder.call(SqlStdOperatorTable.EQUALS,
relBuilder.field(2, 0, "JOB"),
relBuilder.field(2, 1, "JOB")))
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(JoinToMultiJoinRule.INSTANCE)
.build();
HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
RelNode output = hepPlanner.findBestExp();
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
SqlToRelTestBase.assertValid(output);
}
@Test void testPushFilterPastAgg() {
final String sql = "select dname, c from\n"
+ "(select name dname, count(*) as c from dept group by name) t\n"
+ " where dname = 'Charlie'";
sql(sql).withRule(FilterAggregateTransposeRule.INSTANCE).check();
}
private void basePushFilterPastAggWithGroupingSets(boolean unchanged)
throws Exception {
final HepProgram preProgram =
HepProgram.builder()
.addRuleInstance(ProjectMergeRule.INSTANCE)
.addRuleInstance(FilterProjectTransposeRule.INSTANCE)
.build();
final HepProgram program =
HepProgram.builder()
.addRuleInstance(FilterAggregateTransposeRule.INSTANCE)
.build();
Sql sql = sql("${sql}").withPre(preProgram)
.with(program);
if (unchanged) {
sql.checkUnchanged();
} else {
sql.check();
}
}
@Test void testPushFilterPastAggWithGroupingSets1() throws Exception {
basePushFilterPastAggWithGroupingSets(true);
}
@Test void testPushFilterPastAggWithGroupingSets2() throws Exception {
basePushFilterPastAggWithGroupingSets(false);
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-434">[CALCITE-434]
* FilterAggregateTransposeRule loses conditions that cannot be pushed</a>. */
@Test void testPushFilterPastAggTwo() {
final String sql = "select dept1.c1 from (\n"
+ "select dept.name as c1, count(*) as c2\n"
+ "from dept where dept.name > 'b' group by dept.name) dept1\n"
+ "where dept1.c1 > 'c' and (dept1.c2 > 30 or dept1.c1 < 'z')";
sql(sql).withRule(FilterAggregateTransposeRule.INSTANCE).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-799">[CALCITE-799]
* Incorrect result for {@code HAVING count(*) > 1}</a>. */
@Test void testPushFilterPastAggThree() {
final String sql = "select deptno from emp\n"
+ "group by deptno having count(*) > 1";
sql(sql).withRule(FilterAggregateTransposeRule.INSTANCE)
.checkUnchanged();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1109">[CALCITE-1109]
* FilterAggregateTransposeRule pushes down incorrect condition</a>. */
@Test void testPushFilterPastAggFour() {
final HepProgram preProgram =
HepProgram.builder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateFilterTransposeRule.INSTANCE)
.build();
final HepProgram program =
HepProgram.builder()
.addRuleInstance(FilterAggregateTransposeRule.INSTANCE)
.build();
final String sql = "select emp.deptno, count(*) from emp where emp.sal > '12'\n"
+ "group by emp.deptno";
sql(sql)
.withPre(preProgram)
.with(program)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-448">[CALCITE-448]
* FilterIntoJoinRule creates filters containing invalid RexInputRef</a>. */
@Test void testPushFilterPastProject() {
final HepProgram preProgram =
HepProgram.builder()
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final FilterJoinRule.Predicate predicate =
(join, joinType, exp) -> joinType != JoinRelType.INNER;
final FilterJoinRule join =
new FilterJoinRule.JoinConditionPushRule(RelBuilder.proto(), predicate);
final FilterJoinRule filterOnJoin =
new FilterJoinRule.FilterIntoJoinRule(true, RelBuilder.proto(),
predicate);
final HepProgram program =
HepProgram.builder()
.addGroupBegin()
.addRuleInstance(FilterProjectTransposeRule.INSTANCE)
.addRuleInstance(join)
.addRuleInstance(filterOnJoin)
.addGroupEnd()
.build();
final String sql = "select a.name\n"
+ "from dept a\n"
+ "left join dept b on b.deptno > 10\n"
+ "right join dept c on b.deptno > 10\n";
sql(sql).withPre(preProgram)
.with(program)
.check();
}
@Test void testJoinProjectTranspose1() {
final HepProgram preProgram =
HepProgram.builder()
.addRuleInstance(ProjectJoinTransposeRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final HepProgram program =
HepProgram.builder()
.addRuleInstance(JoinProjectTransposeRule.LEFT_PROJECT_INCLUDE_OUTER)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.addRuleInstance(JoinProjectTransposeRule.RIGHT_PROJECT_INCLUDE_OUTER)
.addRuleInstance(JoinProjectTransposeRule.LEFT_PROJECT_INCLUDE_OUTER)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final String sql = "select a.name\n"
+ "from dept a\n"
+ "left join dept b on b.deptno > 10\n"
+ "right join dept c on b.deptno > 10\n";
sql(sql).withPre(preProgram)
.with(program)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1338">[CALCITE-1338]
* JoinProjectTransposeRule should not pull a literal above the
* null-generating side of a join</a>. */
@Test void testJoinProjectTranspose2() {
final String sql = "select *\n"
+ "from dept a\n"
+ "left join (select name, 1 from dept) as b\n"
+ "on a.name = b.name";
sql(sql)
.withRule(JoinProjectTransposeRule.RIGHT_PROJECT_INCLUDE_OUTER)
.checkUnchanged();
}
/** As {@link #testJoinProjectTranspose2()};
* should not transpose since the left project of right join has literal. */
@Test void testJoinProjectTranspose3() {
final String sql = "select *\n"
+ "from (select name, 1 from dept) as a\n"
+ "right join dept b\n"
+ "on a.name = b.name";
sql(sql)
.withRule(JoinProjectTransposeRule.LEFT_PROJECT_INCLUDE_OUTER)
.checkUnchanged();
}
/** As {@link #testJoinProjectTranspose2()};
* should not transpose since the right project of left join has not-strong
* expression {@code y is not null}. */
@Test void testJoinProjectTranspose4() {
final String sql = "select *\n"
+ "from dept a\n"
+ "left join (select x name, y is not null from\n"
+ "(values (2, cast(null as integer)), (2, 1)) as t(x, y)) b\n"
+ "on a.name = b.name";
sql(sql)
.withRule(JoinProjectTransposeRule.RIGHT_PROJECT_INCLUDE_OUTER)
.checkUnchanged();
}
/** As {@link #testJoinProjectTranspose2()};
* should not transpose since the right project of left join has not-strong
* expression {@code 1 + 1}. */
@Test void testJoinProjectTranspose5() {
final String sql = "select *\n"
+ "from dept a\n"
+ "left join (select name, 1 + 1 from dept) as b\n"
+ "on a.name = b.name";
sql(sql)
.withRule(JoinProjectTransposeRule.RIGHT_PROJECT_INCLUDE_OUTER)
.checkUnchanged();
}
/** As {@link #testJoinProjectTranspose2()};
* should not transpose since both the left project and right project have
* literal. */
@Test void testJoinProjectTranspose6() {
final String sql = "select *\n"
+ "from (select name, 1 from dept) a\n"
+ "full join (select name, 1 from dept) as b\n"
+ "on a.name = b.name";
sql(sql)
.withRule(JoinProjectTransposeRule.RIGHT_PROJECT_INCLUDE_OUTER)
.checkUnchanged();
}
/** As {@link #testJoinProjectTranspose2()};
* Should transpose since all expressions in the right project of left join
* are strong. */
@Test void testJoinProjectTranspose7() {
final String sql = "select *\n"
+ "from dept a\n"
+ "left join (select name from dept) as b\n"
+ " on a.name = b.name";
sql(sql)
.withRule(JoinProjectTransposeRule.RIGHT_PROJECT_INCLUDE_OUTER)
.check();
}
/** As {@link #testJoinProjectTranspose2()};
* should transpose since all expressions including
* {@code deptno > 10 and cast(null as boolean)} in the right project of left
* join are strong. */
@Test void testJoinProjectTranspose8() {
final String sql = "select *\n"
+ "from dept a\n"
+ "left join (\n"
+ " select name, deptno > 10 and cast(null as boolean)\n"
+ " from dept) as b\n"
+ "on a.name = b.name";
sql(sql)
.withRule(JoinProjectTransposeRule.RIGHT_PROJECT_INCLUDE_OUTER)
.check();
}
@Test void testJoinProjectTransposeWindow() {
final String sql = "select *\n"
+ "from dept a\n"
+ "join (select rank() over (order by name) as r, 1 + 1 from dept) as b\n"
+ "on a.name = b.r";
sql(sql)
.withRule(JoinProjectTransposeRule.BOTH_PROJECT)
.check();
}
/**
* Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3353">[CALCITE-3353]
* ProjectJoinTransposeRule caused AssertionError when creating a new Join</a>.
*/
@Test void testProjectJoinTransposeWithMergeJoin() {
ProjectJoinTransposeRule testRule = new ProjectJoinTransposeRule(
Project.class, Join.class, expr -> !(expr instanceof RexOver),
RelFactories.LOGICAL_BUILDER);
ImmutableList<RelOptRule> commonRules = ImmutableList.of(
EnumerableRules.ENUMERABLE_PROJECT_RULE,
EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE,
EnumerableRules.ENUMERABLE_SORT_RULE,
EnumerableRules.ENUMERABLE_VALUES_RULE);
final RuleSet rules = RuleSets.ofList(ImmutableList.<RelOptRule>builder()
.addAll(commonRules)
.add(ProjectJoinTransposeRule.INSTANCE)
.build());
final RuleSet testRules = RuleSets.ofList(ImmutableList.<RelOptRule>builder()
.addAll(commonRules)
.add(testRule).build());
FrameworkConfig config = Frameworks.newConfigBuilder()
.parserConfig(SqlParser.Config.DEFAULT)
.traitDefs(ConventionTraitDef.INSTANCE, RelCollationTraitDef.INSTANCE)
.build();
RelBuilder builder = RelBuilder.create(config);
RelNode logicalPlan = builder
.values(new String[]{"id", "name"}, "1", "anna", "2", "bob", "3", "tom")
.values(new String[]{"name", "age"}, "anna", "14", "bob", "17", "tom", "22")
.join(JoinRelType.INNER, "name")
.project(builder.field(3))
.build();
RelTraitSet desiredTraits = logicalPlan.getTraitSet()
.replace(EnumerableConvention.INSTANCE);
RelOptPlanner planner = logicalPlan.getCluster().getPlanner();
RelNode enumerablePlan1 = Programs.of(rules).run(planner, logicalPlan,
desiredTraits, ImmutableList.of(), ImmutableList.of());
RelNode enumerablePlan2 = Programs.of(testRules).run(planner, logicalPlan,
desiredTraits, ImmutableList.of(), ImmutableList.of());
assertEquals(RelOptUtil.toString(enumerablePlan1), RelOptUtil.toString(enumerablePlan2));
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-889">[CALCITE-889]
* Implement SortUnionTransposeRule</a>. */
@Test void testSortUnionTranspose() {
final HepProgram program =
HepProgram.builder()
.addRuleInstance(ProjectSetOpTransposeRule.INSTANCE)
.addRuleInstance(SortUnionTransposeRule.INSTANCE)
.build();
final String sql = "select a.name from dept a\n"
+ "union all\n"
+ "select b.name from dept b\n"
+ "order by name limit 10";
sql(sql).with(program).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-889">[CALCITE-889]
* Implement SortUnionTransposeRule</a>. */
@Test void testSortUnionTranspose2() {
final HepProgram program =
HepProgram.builder()
.addRuleInstance(ProjectSetOpTransposeRule.INSTANCE)
.addRuleInstance(SortUnionTransposeRule.MATCH_NULL_FETCH)
.build();
final String sql = "select a.name from dept a\n"
+ "union all\n"
+ "select b.name from dept b\n"
+ "order by name";
sql(sql).with(program).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-987">[CALCITE-987]
* Push limit 0 will result in an infinite loop</a>. */
@Test void testSortUnionTranspose3() {
final HepProgram program =
HepProgram.builder()
.addRuleInstance(ProjectSetOpTransposeRule.INSTANCE)
.addRuleInstance(SortUnionTransposeRule.MATCH_NULL_FETCH)
.build();
final String sql = "select a.name from dept a\n"
+ "union all\n"
+ "select b.name from dept b\n"
+ "order by name limit 0";
sql(sql).with(program).check();
}
@Test void testSortRemovalAllKeysConstant() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(SortRemoveConstantKeysRule.INSTANCE)
.build();
final String sql = "select count(*) as c\n"
+ "from sales.emp\n"
+ "where deptno = 10\n"
+ "group by deptno, sal\n"
+ "order by deptno desc nulls last";
sql(sql).with(program).check();
}
@Test void testSortRemovalOneKeyConstant() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(SortRemoveConstantKeysRule.INSTANCE)
.build();
final String sql = "select count(*) as c\n"
+ "from sales.emp\n"
+ "where deptno = 10\n"
+ "group by deptno, sal\n"
+ "order by deptno, sal desc nulls first";
sql(sql).with(program).check();
}
@Test void testSemiJoinRuleExists() {
final HepProgram preProgram =
HepProgram.builder()
.addRuleInstance(FilterProjectTransposeRule.INSTANCE)
.addRuleInstance(FilterJoinRule.FILTER_ON_JOIN)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final HepProgram program =
HepProgram.builder()
.addRuleInstance(SemiJoinRule.PROJECT)
.build();
final String sql = "select * from dept where exists (\n"
+ " select * from emp\n"
+ " where emp.deptno = dept.deptno\n"
+ " and emp.sal > 100)";
sql(sql)
.withDecorrelation(true)
.withTrim(true)
.withPre(preProgram)
.with(program)
.check();
}
@Test void testSemiJoinRule() {
final HepProgram preProgram =
HepProgram.builder()
.addRuleInstance(FilterProjectTransposeRule.INSTANCE)
.addRuleInstance(FilterJoinRule.FILTER_ON_JOIN)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final HepProgram program =
HepProgram.builder()
.addRuleInstance(SemiJoinRule.PROJECT)
.build();
final String sql = "select dept.* from dept join (\n"
+ " select distinct deptno from emp\n"
+ " where sal > 100) using (deptno)";
sql(sql)
.withDecorrelation(true)
.withTrim(true)
.withPre(preProgram)
.with(program)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1495">[CALCITE-1495]
* SemiJoinRule should not apply to RIGHT and FULL JOIN</a>. */
@Test void testSemiJoinRuleRight() {
final HepProgram preProgram =
HepProgram.builder()
.addRuleInstance(FilterProjectTransposeRule.INSTANCE)
.addRuleInstance(FilterJoinRule.FILTER_ON_JOIN)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final HepProgram program =
HepProgram.builder()
.addRuleInstance(SemiJoinRule.PROJECT)
.build();
final String sql = "select dept.* from dept right join (\n"
+ " select distinct deptno from emp\n"
+ " where sal > 100) using (deptno)";
sql(sql)
.withPre(preProgram)
.with(program)
.withDecorrelation(true)
.withTrim(true)
.checkUnchanged();
}
/** Similar to {@link #testSemiJoinRuleRight()} but FULL. */
@Test void testSemiJoinRuleFull() {
final HepProgram preProgram =
HepProgram.builder()
.addRuleInstance(FilterProjectTransposeRule.INSTANCE)
.addRuleInstance(FilterJoinRule.FILTER_ON_JOIN)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final HepProgram program =
HepProgram.builder()
.addRuleInstance(SemiJoinRule.PROJECT)
.build();
final String sql = "select dept.* from dept full join (\n"
+ " select distinct deptno from emp\n"
+ " where sal > 100) using (deptno)";
sql(sql)
.withPre(preProgram)
.with(program)
.withDecorrelation(true)
.withTrim(true)
.checkUnchanged();
}
/** Similar to {@link #testSemiJoinRule()} but LEFT. */
@Test void testSemiJoinRuleLeft() {
final HepProgram preProgram =
HepProgram.builder()
.addRuleInstance(FilterProjectTransposeRule.INSTANCE)
.addRuleInstance(FilterJoinRule.FILTER_ON_JOIN)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final HepProgram program =
HepProgram.builder()
.addRuleInstance(SemiJoinRule.PROJECT)
.build();
final String sql = "select name from dept left join (\n"
+ " select distinct deptno from emp\n"
+ " where sal > 100) using (deptno)";
sql(sql)
.withPre(preProgram)
.with(program)
.withDecorrelation(true)
.withTrim(true)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-438">[CALCITE-438]
* Push predicates through SemiJoin</a>. */
@Test void testPushFilterThroughSemiJoin() {
final HepProgram preProgram =
HepProgram.builder()
.addRuleInstance(SemiJoinRule.PROJECT)
.build();
final HepProgram program =
HepProgram.builder()
.addRuleInstance(FilterProjectTransposeRule.INSTANCE)
.addRuleInstance(FilterJoinRule.FILTER_ON_JOIN)
.addRuleInstance(FilterJoinRule.JOIN)
.build();
final String sql = "select * from (\n"
+ " select * from dept where dept.deptno in (\n"
+ " select emp.deptno from emp))R\n"
+ "where R.deptno <=10";
sql(sql)
.withDecorrelation(true)
.withTrim(false)
.withPre(preProgram)
.with(program)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-571">[CALCITE-571]
* ReduceExpressionsRule tries to reduce SemiJoin condition to non-equi
* condition</a>. */
@Test void testSemiJoinReduceConstants() {
final HepProgram preProgram = HepProgram.builder()
.addRuleInstance(SemiJoinRule.PROJECT)
.build();
final HepProgram program = HepProgram.builder()
.addRuleInstance(ReduceExpressionsRule.JOIN_INSTANCE)
.build();
final String sql = "select e1.sal\n"
+ "from (select * from emp where deptno = 200) as e1\n"
+ "where e1.deptno in (\n"
+ " select e2.deptno from emp e2 where e2.sal = 100)";
sql(sql)
.withDecorrelation(false)
.withTrim(true)
.withPre(preProgram)
.with(program)
.check();
}
@Test void testSemiJoinTrim() throws Exception {
final DiffRepository diffRepos = getDiffRepos();
String sql = diffRepos.expand(null, "${sql}");
TesterImpl t = (TesterImpl) tester;
final RelDataTypeFactory typeFactory = t.getTypeFactory();
final Prepare.CatalogReader catalogReader =
t.createCatalogReader(typeFactory);
final SqlValidator validator =
t.createValidator(
catalogReader, typeFactory);
SqlToRelConverter converter =
t.createSqlToRelConverter(
validator,
catalogReader,
typeFactory,
SqlToRelConverter.Config.DEFAULT);
final SqlNode sqlQuery = t.parseQuery(sql);
final SqlNode validatedQuery = validator.validate(sqlQuery);
RelRoot root =
converter.convertQuery(validatedQuery, false, true);
root = root.withRel(converter.decorrelate(sqlQuery, root.rel));
final HepProgram program =
HepProgram.builder()
.addRuleInstance(FilterProjectTransposeRule.INSTANCE)
.addRuleInstance(FilterJoinRule.FILTER_ON_JOIN)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.addRuleInstance(SemiJoinRule.PROJECT)
.build();
HepPlanner planner = new HepPlanner(program);
planner.setRoot(root.rel);
root = root.withRel(planner.findBestExp());
String planBefore = NL + RelOptUtil.toString(root.rel);
diffRepos.assertEquals("planBefore", "${planBefore}", planBefore);
converter = t.createSqlToRelConverter(validator, catalogReader, typeFactory,
SqlToRelConverter.configBuilder().withTrimUnusedFields(true).build());
root = root.withRel(converter.trimUnusedFields(false, root.rel));
String planAfter = NL + RelOptUtil.toString(root.rel);
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
}
@Test void testReduceAverage() {
final String sql = "select name, max(name), avg(deptno), min(name)\n"
+ "from sales.dept group by name";
sql(sql).withRule(AggregateReduceFunctionsRule.INSTANCE).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1621">[CALCITE-1621]
* Adding a cast around the null literal in aggregate rules</a>. */
@Test void testCastInAggregateReduceFunctions() {
final HepProgram program =
HepProgram.builder()
.addRuleInstance(AggregateReduceFunctionsRule.INSTANCE)
.build();
final String sql = "select name, stddev_pop(deptno), avg(deptno),\n"
+ "stddev_samp(deptno),var_pop(deptno), var_samp(deptno)\n"
+ "from sales.dept group by name";
sql(sql).with(program).check();
}
@Test void testDistinctCountWithoutGroupBy() {
final HepProgram program = HepProgram.builder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.INSTANCE)
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select max(deptno), count(distinct ename)\n"
+ "from sales.emp";
sql(sql).with(program).check();
}
@Test void testDistinctCount1() {
final HepProgram program = HepProgram.builder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.INSTANCE)
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select deptno, count(distinct ename)\n"
+ "from sales.emp group by deptno";
sql(sql).with(program).check();
}
@Test void testDistinctCount2() {
final HepProgram program = HepProgram.builder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.INSTANCE)
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select deptno, count(distinct ename), sum(sal)\n"
+ "from sales.emp group by deptno";
sql(sql).with(program).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1293">[CALCITE-1293]
* Bad code generated when argument to COUNT(DISTINCT) is a # GROUP BY
* column</a>. */
@Test void testDistinctCount3() {
final String sql = "select count(distinct deptno), sum(sal)"
+ " from sales.emp group by deptno";
final HepProgram program = HepProgram.builder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.INSTANCE)
.build();
sql(sql).with(program).check();
}
/** Tests implementing multiple distinct count the old way, using a join. */
@Test void testDistinctCountMultipleViaJoin() {
final HepProgram program = HepProgram.builder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.JOIN)
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select deptno, count(distinct ename), count(distinct job, ename),\n"
+ "count(distinct deptno, job), sum(sal)\n"
+ "from sales.emp group by deptno";
sql(sql).with(program).check();
}
/** Tests implementing multiple distinct count the new way, using GROUPING
* SETS. */
@Test void testDistinctCountMultiple() {
final HepProgram program = HepProgram.builder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.INSTANCE)
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select deptno, count(distinct ename), count(distinct job)\n"
+ "from sales.emp group by deptno";
sql(sql).with(program).check();
}
@Test void testDistinctCountMultipleNoGroup() {
final HepProgram program = HepProgram.builder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.INSTANCE)
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select count(distinct ename), count(distinct job)\n"
+ "from sales.emp";
sql(sql).with(program).check();
}
@Test void testDistinctCountMixedJoin() {
final HepProgram program = HepProgram.builder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.JOIN)
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select deptno, count(distinct ename), count(distinct job, ename),\n"
+ "count(distinct deptno, job), sum(sal)\n"
+ "from sales.emp group by deptno";
sql(sql).with(program).check();
}
@Test void testDistinctCountMixed() {
final HepProgram program = HepProgram.builder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final String sql = "select deptno, count(distinct deptno, job) as cddj, sum(sal) as s\n"
+ "from sales.emp group by deptno";
sql(sql).with(program).check();
}
@Test void testDistinctCountMixed2() {
final HepProgram program = HepProgram.builder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.INSTANCE)
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final String sql = "select deptno, count(distinct ename) as cde,\n"
+ "count(distinct job, ename) as cdje,\n"
+ "count(distinct deptno, job) as cddj,\n"
+ "sum(sal) as s\n"
+ "from sales.emp group by deptno";
sql(sql).with(program).check();
}
@Test void testDistinctCountGroupingSets1() {
final HepProgram program = HepProgram.builder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final String sql = "select deptno, job, count(distinct ename)\n"
+ "from sales.emp group by rollup(deptno,job)";
sql(sql).with(program).check();
}
@Test void testDistinctCountGroupingSets2() {
final HepProgram program = HepProgram.builder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final String sql = "select deptno, job, count(distinct ename), sum(sal)\n"
+ "from sales.emp group by rollup(deptno,job)";
sql(sql).with(program).check();
}
@Test void testDistinctNonDistinctAggregates() {
final String sql = "select emp.empno, count(*), avg(distinct dept.deptno)\n"
+ "from sales.emp emp inner join sales.dept dept\n"
+ "on emp.deptno = dept.deptno\n"
+ "group by emp.empno";
final HepProgram program = HepProgram.builder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.JOIN)
.build();
sql(sql).with(program).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1621">[CALCITE-1621]
* Adding a cast around the null literal in aggregate rules</a>. */
@Test void testCastInAggregateExpandDistinctAggregatesRule() {
final String sql = "select name, sum(distinct cn), sum(distinct sm)\n"
+ "from (\n"
+ " select name, count(dept.deptno) as cn,sum(dept.deptno) as sm\n"
+ " from sales.dept group by name)\n"
+ "group by name";
final HepProgram program = HepProgram.builder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.INSTANCE)
.build();
sql(sql).with(program).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1558">[CALCITE-1558]
* AggregateExpandDistinctAggregatesRule gets field mapping wrong if groupKey
* is used in aggregate function</a>. */
@Test void testDistinctNonDistinctAggregatesWithGrouping1() {
final String sql = "SELECT deptno,\n"
+ " SUM(deptno), SUM(DISTINCT sal), MAX(deptno), MAX(comm)\n"
+ "FROM emp\n"
+ "GROUP BY deptno";
HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.JOIN)
.build();
sql(sql).with(program).check();
}
@Test void testDistinctNonDistinctAggregatesWithGrouping2() {
final String sql = "SELECT deptno, COUNT(deptno), SUM(DISTINCT sal)\n"
+ "FROM emp\n"
+ "GROUP BY deptno";
HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.JOIN)
.build();
sql(sql).with(program).check();
}
@Test void testDistinctNonDistinctTwoAggregatesWithGrouping() {
final String sql = "SELECT deptno, SUM(comm), MIN(comm), SUM(DISTINCT sal)\n"
+ "FROM emp\n"
+ "GROUP BY deptno";
HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.JOIN)
.build();
sql(sql).with(program).check();
}
@Test void testDistinctWithGrouping() {
final String sql = "SELECT sal, SUM(comm), MIN(comm), SUM(DISTINCT sal)\n"
+ "FROM emp\n"
+ "GROUP BY sal";
HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.JOIN)
.build();
sql(sql).with(program).check();
}
@Test void testRemoveDistinctOnAgg() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateRemoveRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final String sql = "SELECT empno, SUM(distinct sal), MIN(sal), "
+ "MIN(distinct sal), MAX(distinct sal), "
+ "bit_and(distinct sal), bit_or(sal), count(distinct sal) "
+ "from sales.emp group by empno, deptno\n";
sql(sql).with(program).check();
}
@Test void testMultipleDistinctWithGrouping() {
final String sql = "SELECT sal, SUM(comm), AVG(DISTINCT comm), SUM(DISTINCT sal)\n"
+ "FROM emp\n"
+ "GROUP BY sal";
HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.JOIN)
.build();
sql(sql).with(program).check();
}
@Test void testDistinctWithMultipleInputs() {
final String sql = "SELECT deptno, SUM(comm), MIN(comm), COUNT(DISTINCT sal, comm)\n"
+ "FROM emp\n"
+ "GROUP BY deptno";
HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.JOIN)
.build();
sql(sql).with(program).check();
}
@Test void testDistinctWithMultipleInputsAndGroupby() {
final String sql = "SELECT deptno, SUM(comm), MIN(comm), COUNT(DISTINCT sal, deptno, comm)\n"
+ "FROM emp\n"
+ "GROUP BY deptno";
HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.JOIN)
.build();
sql(sql).with(program).check();
}
@Test void testDistinctWithFilterWithoutGroupBy() {
final String sql = "SELECT SUM(comm), COUNT(DISTINCT sal) FILTER (WHERE sal > 1000)\n"
+ "FROM emp";
HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.INSTANCE)
.build();
sql(sql).with(program).check();
}
@Test void testDistinctWithDiffFiltersAndSameGroupSet() {
final String sql = "SELECT COUNT(DISTINCT c) FILTER (WHERE d),\n"
+ "COUNT(DISTINCT d) FILTER (WHERE c)\n"
+ "FROM (select sal > 1000 is true as c, sal < 500 is true as d, comm from emp)";
HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.INSTANCE)
.build();
sql(sql).with(program).check();
}
@Test void testDistinctWithFilterAndGroupBy() {
final String sql = "SELECT deptno, SUM(comm), COUNT(DISTINCT sal) FILTER (WHERE sal > 1000)\n"
+ "FROM emp\n"
+ "GROUP BY deptno";
HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateExpandDistinctAggregatesRule.INSTANCE)
.build();
sql(sql).with(program).check();
}
@Test void testPushProjectPastFilter() {
final String sql = "select empno + deptno from emp where sal = 10 * comm\n"
+ "and upper(ename) = 'FOO'";
sql(sql).withRule(ProjectFilterTransposeRule.INSTANCE).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1778">[CALCITE-1778]
* Query with "WHERE CASE" throws AssertionError "Cast for just nullability
* not allowed"</a>. */
@Test void testPushProjectPastFilter2() {
final String sql = "select count(*)\n"
+ "from emp\n"
+ "where case when mgr < 10 then true else false end";
sql(sql).withRule(ProjectFilterTransposeRule.INSTANCE).check();
}
@Test void testPushProjectPastJoin() {
final String sql = "select e.sal + b.comm from emp e inner join bonus b\n"
+ "on e.ename = b.ename and e.deptno = 10";
sql(sql).withRule(ProjectJoinTransposeRule.INSTANCE).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3004">[CALCITE-3004]
* Should not push over past union but its operands can since setop
* will affect row count</a>. */
@Test void testProjectSetOpTranspose() {
final String sql = "select job, sum(sal + 100) over (partition by deptno) from\n"
+ "(select * from emp e1 union all select * from emp e2)";
sql(sql).withRule(ProjectSetOpTransposeRule.INSTANCE).check();
}
@Test void testProjectCorrelateTransposeDynamic() {
ProjectCorrelateTransposeRule customPCTrans =
new ProjectCorrelateTransposeRule(skipItem, RelFactories.LOGICAL_BUILDER);
HepProgramBuilder programBuilder = HepProgram.builder()
.addRuleInstance(customPCTrans);
String query = "select t1.c_nationkey, t2.a as fake_col2 "
+ "from SALES.CUSTOMER as t1, "
+ "unnest(t1.fake_col) as t2(a)";
sql(query).withTester(t -> createDynamicTester())
.with(programBuilder.build())
.checkUnchanged();
}
@Test void testProjectCorrelateTransposeRuleLeftCorrelate() {
final String sql = "SELECT e1.empno\n"
+ "FROM emp e1 "
+ "where exists (select empno, deptno from dept d2 where e1.deptno = d2.deptno)";
HepProgram program = new HepProgramBuilder()
.addRuleInstance(FilterProjectTransposeRule.INSTANCE)
.addRuleInstance(ProjectFilterTransposeRule.INSTANCE)
.addRuleInstance(ProjectCorrelateTransposeRule.INSTANCE)
.build();
sql(sql)
.withDecorrelation(false)
.expand(true)
.with(program)
.check();
}
@Test void testProjectCorrelateTransposeRuleSemiCorrelate() {
RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
RelNode left = relBuilder
.values(new String[]{"f", "f2"}, "1", "2").build();
CorrelationId correlationId = new CorrelationId(0);
RexNode rexCorrel =
relBuilder.getRexBuilder().makeCorrel(
left.getRowType(),
correlationId);
RelNode right = relBuilder
.values(new String[]{"f3", "f4"}, "1", "2")
.project(relBuilder.field(0),
relBuilder.getRexBuilder()
.makeFieldAccess(rexCorrel, 0))
.build();
LogicalCorrelate correlate = new LogicalCorrelate(left.getCluster(),
left.getTraitSet(), left, right, correlationId,
ImmutableBitSet.of(0), JoinRelType.SEMI);
relBuilder.push(correlate);
RelNode relNode = relBuilder.project(relBuilder.field(0))
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ProjectCorrelateTransposeRule.INSTANCE)
.build();
HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
RelNode output = hepPlanner.findBestExp();
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
SqlToRelTestBase.assertValid(output);
}
@Test void testProjectCorrelateTransposeRuleAntiCorrelate() {
RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
RelNode left = relBuilder
.values(new String[]{"f", "f2"}, "1", "2").build();
CorrelationId correlationId = new CorrelationId(0);
RexNode rexCorrel =
relBuilder.getRexBuilder().makeCorrel(
left.getRowType(),
correlationId);
RelNode right = relBuilder
.values(new String[]{"f3", "f4"}, "1", "2")
.project(relBuilder.field(0),
relBuilder.getRexBuilder().makeFieldAccess(rexCorrel, 0)).build();
LogicalCorrelate correlate = new LogicalCorrelate(left.getCluster(),
left.getTraitSet(), left, right, correlationId,
ImmutableBitSet.of(0), JoinRelType.ANTI);
relBuilder.push(correlate);
RelNode relNode = relBuilder.project(relBuilder.field(0))
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ProjectCorrelateTransposeRule.INSTANCE)
.build();
HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
RelNode output = hepPlanner.findBestExp();
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
SqlToRelTestBase.assertValid(output);
}
@Test void testProjectCorrelateTransposeWithExprCond() {
ProjectCorrelateTransposeRule customPCTrans =
new ProjectCorrelateTransposeRule(skipItem, RelFactories.LOGICAL_BUILDER);
final String sql = "select t1.name, t2.ename\n"
+ "from DEPT_NESTED as t1,\n"
+ "unnest(t1.employees) as t2";
sql(sql).withRule(customPCTrans).check();
}
@Test void testSwapOuterJoinFieldAccess() {
HepProgram preProgram = new HepProgramBuilder()
.addMatchLimit(1)
.addRuleInstance(JoinProjectTransposeRule.LEFT_PROJECT_INCLUDE_OUTER)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final HepProgram program = new HepProgramBuilder()
.addMatchLimit(1)
.addRuleInstance(JoinCommuteRule.SWAP_OUTER)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final String sql = "select t1.name, e.ename\n"
+ "from DEPT_NESTED as t1 left outer join sales.emp e\n"
+ " on t1.skill.type = e.job";
sql(sql).withPre(preProgram).with(program).check();
}
@Test void testProjectCorrelateTranspose() {
ProjectCorrelateTransposeRule customPCTrans =
new ProjectCorrelateTransposeRule(expr -> true,
RelFactories.LOGICAL_BUILDER);
final String sql = "select t1.name, t2.ename\n"
+ "from DEPT_NESTED as t1,\n"
+ "unnest(t1.employees) as t2";
sql(sql).withRule(customPCTrans).check();
}
/** As {@link #testProjectSetOpTranspose()};
* should not push over past correlate but its operands can since correlate
* will affect row count. */
@Test void testProjectCorrelateTransposeWithOver() {
final String sql = "select sum(t1.deptno + 1) over (partition by t1.name),\n"
+ "count(t2.empno) over ()\n"
+ "from DEPT_NESTED as t1,\n"
+ "unnest(t1.employees) as t2";
sql(sql).withRule(ProjectCorrelateTransposeRule.INSTANCE).check();
}
/** Tests that the default instance of {@link FilterProjectTransposeRule}
* does not push a Filter that contains a correlating variable.
*
* @see #testFilterProjectTranspose() */
@Test void testFilterProjectTransposePreventedByCorrelation() {
final String sql = "SELECT e.empno\n"
+ "FROM emp as e\n"
+ "WHERE exists (\n"
+ " SELECT *\n"
+ " FROM (\n"
+ " SELECT deptno * 2 AS twiceDeptno\n"
+ " FROM dept) AS d\n"
+ " WHERE e.deptno = d.twiceDeptno)";
HepProgram program = new HepProgramBuilder()
.addRuleInstance(FilterProjectTransposeRule.INSTANCE)
.build();
sql(sql)
.withDecorrelation(false)
.expand(true)
.with(program)
.checkUnchanged();
}
/** Tests a variant of {@link FilterProjectTransposeRule}
* that pushes a Filter that contains a correlating variable. */
@Test void testFilterProjectTranspose() {
final String sql = "SELECT e.empno\n"
+ "FROM emp as e\n"
+ "WHERE exists (\n"
+ " SELECT *\n"
+ " FROM (\n"
+ " SELECT deptno * 2 AS twiceDeptno\n"
+ " FROM dept) AS d\n"
+ " WHERE e.deptno = d.twiceDeptno)";
final FilterProjectTransposeRule filterProjectTransposeRule =
new FilterProjectTransposeRule(Filter.class, filter -> true,
Project.class, project -> true, true, true,
RelFactories.LOGICAL_BUILDER);
HepProgram program = new HepProgramBuilder()
.addRuleInstance(filterProjectTransposeRule)
.build();
sql(sql)
.withDecorrelation(false)
.expand(true)
.with(program)
.check();
}
private static final String NOT_STRONG_EXPR =
"case when e.sal < 11 then 11 else -1 * e.sal end";
private static final String STRONG_EXPR =
"case when e.sal < 11 then -1 * e.sal else e.sal end";
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1753">[CALCITE-1753]
* PushProjector should only preserve expressions if the expression is strong
* when pushing into the nullable-side of outer join</a>. */
@Test void testPushProjectPastInnerJoin() {
final String sql = "select count(*), " + NOT_STRONG_EXPR + "\n"
+ "from emp e inner join bonus b on e.ename = b.ename\n"
+ "group by " + NOT_STRONG_EXPR;
sql(sql).withRule(ProjectJoinTransposeRule.INSTANCE).check();
}
@Test void testPushProjectPastInnerJoinStrong() {
final String sql = "select count(*), " + STRONG_EXPR + "\n"
+ "from emp e inner join bonus b on e.ename = b.ename\n"
+ "group by " + STRONG_EXPR;
sql(sql).withRule(ProjectJoinTransposeRule.INSTANCE).check();
}
@Test void testPushProjectPastLeftJoin() {
final String sql = "select count(*), " + NOT_STRONG_EXPR + "\n"
+ "from emp e left outer join bonus b on e.ename = b.ename\n"
+ "group by case when e.sal < 11 then 11 else -1 * e.sal end";
sql(sql).withRule(ProjectJoinTransposeRule.INSTANCE).check();
}
@Test void testPushProjectPastLeftJoinSwap() {
final String sql = "select count(*), " + NOT_STRONG_EXPR + "\n"
+ "from bonus b left outer join emp e on e.ename = b.ename\n"
+ "group by " + NOT_STRONG_EXPR;
sql(sql).withRule(ProjectJoinTransposeRule.INSTANCE).check();
}
@Test void testPushProjectPastLeftJoinSwapStrong() {
final String sql = "select count(*), " + STRONG_EXPR + "\n"
+ "from bonus b left outer join emp e on e.ename = b.ename\n"
+ "group by " + STRONG_EXPR;
sql(sql).withRule(ProjectJoinTransposeRule.INSTANCE).check();
}
@Test void testPushProjectPastRightJoin() {
final String sql = "select count(*), " + NOT_STRONG_EXPR + "\n"
+ "from emp e right outer join bonus b on e.ename = b.ename\n"
+ "group by " + NOT_STRONG_EXPR;
sql(sql).withRule(ProjectJoinTransposeRule.INSTANCE).check();
}
@Test void testPushProjectPastRightJoinStrong() {
final String sql = "select count(*),\n"
+ " case when e.sal < 11 then -1 * e.sal else e.sal end\n"
+ "from emp e right outer join bonus b on e.ename = b.ename\n"
+ "group by case when e.sal < 11 then -1 * e.sal else e.sal end";
sql(sql).withRule(ProjectJoinTransposeRule.INSTANCE).check();
}
@Test void testPushProjectPastRightJoinSwap() {
final String sql = "select count(*), " + NOT_STRONG_EXPR + "\n"
+ "from bonus b right outer join emp e on e.ename = b.ename\n"
+ "group by " + NOT_STRONG_EXPR;
sql(sql).withRule(ProjectJoinTransposeRule.INSTANCE).check();
}
@Test void testPushProjectPastRightJoinSwapStrong() {
final String sql = "select count(*), " + STRONG_EXPR + "\n"
+ "from bonus b right outer join emp e on e.ename = b.ename\n"
+ "group by " + STRONG_EXPR;
sql(sql).withRule(ProjectJoinTransposeRule.INSTANCE).check();
}
@Test void testPushProjectPastFullJoin() {
final String sql = "select count(*), " + NOT_STRONG_EXPR + "\n"
+ "from emp e full outer join bonus b on e.ename = b.ename\n"
+ "group by " + NOT_STRONG_EXPR;
sql(sql).withRule(ProjectJoinTransposeRule.INSTANCE).check();
}
@Test void testPushProjectPastFullJoinStrong() {
final String sql = "select count(*), " + STRONG_EXPR + "\n"
+ "from emp e full outer join bonus b on e.ename = b.ename\n"
+ "group by " + STRONG_EXPR;
sql(sql).withRule(ProjectJoinTransposeRule.INSTANCE).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2343">[CALCITE-2343]
* Should not push over whose columns are all from left child past join since
* join will affect row count.</a>. */
@Test void testPushProjectWithOverPastJoin1() {
final String sql = "select e.sal + b.comm,\n"
+ "count(e.empno) over (partition by e.deptno)\n"
+ "from emp e join bonus b\n"
+ "on e.ename = b.ename and e.deptno = 10";
sql(sql).withRule(ProjectJoinTransposeRule.INSTANCE).check();
}
/** As {@link #testPushProjectWithOverPastJoin1()};
* should not push over whose columns are all from right child past join since
* join will affect row count. */
@Test void testPushProjectWithOverPastJoin2() {
final String sql = "select e.sal + b.comm,\n"
+ "count(b.sal) over (partition by b.job)\n"
+ "from emp e join bonus b\n"
+ "on e.ename = b.ename and e.deptno = 10";
sql(sql).withRule(ProjectJoinTransposeRule.INSTANCE).check();
}
/** As {@link #testPushProjectWithOverPastJoin2()};
* should not push over past join but should push the operands of over past
* join. */
@Test void testPushProjectWithOverPastJoin3() {
final String sql = "select e.sal + b.comm,\n"
+ "sum(b.sal + b.sal + 100) over (partition by b.job)\n"
+ "from emp e join bonus b\n"
+ "on e.ename = b.ename and e.deptno = 10";
sql(sql).withRule(ProjectJoinTransposeRule.INSTANCE).check();
}
@Test void testPushProjectPastSetOp() {
final String sql = "select sal from\n"
+ "(select * from emp e1 union all select * from emp e2)";
sql(sql).withRule(ProjectSetOpTransposeRule.INSTANCE).check();
}
@Test void testPushJoinThroughUnionOnLeft() {
final String sql = "select r1.sal from\n"
+ "(select * from emp e1 union all select * from emp e2) r1,\n"
+ "emp r2";
sql(sql).withRule(JoinUnionTransposeRule.LEFT_UNION).check();
}
@Test void testPushJoinThroughUnionOnRight() {
final String sql = "select r1.sal from\n"
+ "emp r1,\n"
+ "(select * from emp e1 union all select * from emp e2) r2";
sql(sql).withRule(JoinUnionTransposeRule.RIGHT_UNION).check();
}
@Test void testPushJoinThroughUnionOnRightDoesNotMatchSemiJoin() {
final RelBuilder builder = RelBuilder.create(RelBuilderTest.config().build());
// build a rel equivalent to sql:
// select r1.sal from
// emp r1 where r1.deptno in
// (select deptno from dept d1 where deptno > 100
// union all
// select deptno from dept d2 where deptno > 20)
RelNode left = builder.scan("EMP").build();
RelNode right = builder
.scan("DEPT")
.filter(
builder.call(SqlStdOperatorTable.GREATER_THAN,
builder.field("DEPTNO"),
builder.literal(100)))
.project(builder.field("DEPTNO"))
.scan("DEPT")
.filter(
builder.call(SqlStdOperatorTable.GREATER_THAN,
builder.field("DEPTNO"),
builder.literal(20)))
.project(builder.field("DEPTNO"))
.union(true)
.build();
RelNode relNode = builder.push(left).push(right)
.semiJoin(
builder.call(SqlStdOperatorTable.EQUALS,
builder.field(2, 0, "DEPTNO"),
builder.field(2, 1, "DEPTNO")))
.project(builder.field("SAL"))
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(JoinUnionTransposeRule.RIGHT_UNION)
.build();
HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
RelNode output = hepPlanner.findBestExp();
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
SqlToRelTestBase.assertValid(output);
}
@Test void testPushJoinThroughUnionOnRightDoesNotMatchAntiJoin() {
final RelBuilder builder = RelBuilder.create(RelBuilderTest.config().build());
// build a rel equivalent to sql:
// select r1.sal from
// emp r1 where r1.deptno not in
// (select deptno from dept d1 where deptno < 10
// union all
// select deptno from dept d2 where deptno > 20)
RelNode left = builder.scan("EMP").build();
RelNode right = builder
.scan("DEPT")
.filter(
builder.call(SqlStdOperatorTable.LESS_THAN,
builder.field("DEPTNO"),
builder.literal(10)))
.project(builder.field("DEPTNO"))
.scan("DEPT")
.filter(
builder.call(SqlStdOperatorTable.GREATER_THAN,
builder.field("DEPTNO"),
builder.literal(20)))
.project(builder.field("DEPTNO"))
.union(true)
.build();
RelNode relNode = builder.push(left).push(right)
.antiJoin(
builder.call(SqlStdOperatorTable.EQUALS,
builder.field(2, 0, "DEPTNO"),
builder.field(2, 1, "DEPTNO")))
.project(builder.field("SAL"))
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(JoinUnionTransposeRule.RIGHT_UNION)
.build();
HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
RelNode output = hepPlanner.findBestExp();
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
SqlToRelTestBase.assertValid(output);
}
@Test void testMergeFilterWithJoinCondition() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(JoinExtractFilterRule.INSTANCE)
.addRuleInstance(FilterToCalcRule.INSTANCE)
.addRuleInstance(ProjectToCalcRule.INSTANCE)
.addRuleInstance(CalcMergeRule.INSTANCE)
.build();
final String sql = "select d.name as dname,e.ename as ename\n"
+ " from emp e inner join dept d\n"
+ " on e.deptno=d.deptno\n"
+ " where d.name='Propane'";
sql(sql).with(program).check();
}
/** Tests that filters are combined if they are identical. */
@Test void testMergeFilter() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(FilterProjectTransposeRule.INSTANCE)
.addRuleInstance(FilterMergeRule.INSTANCE)
.build();
final String sql = "select name from (\n"
+ " select *\n"
+ " from dept\n"
+ " where deptno = 10)\n"
+ "where deptno = 10\n";
sql(sql).with(program).check();
}
/** Tests to see if the final branch of union is missed */
@Test void testUnionMergeRule() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ProjectSetOpTransposeRule.INSTANCE)
.addRuleInstance(ProjectRemoveRule.INSTANCE)
.addRuleInstance(UnionMergeRule.INSTANCE)
.build();
final String sql = "select * from (\n"
+ "select * from (\n"
+ " select name, deptno from dept\n"
+ " union all\n"
+ " select name, deptno from\n"
+ " (\n"
+ " select name, deptno, count(1) from dept group by name, deptno\n"
+ " union all\n"
+ " select name, deptno, count(1) from dept group by name, deptno\n"
+ " ) subq\n"
+ ") a\n"
+ "union all\n"
+ "select name, deptno from dept\n"
+ ") aa\n";
sql(sql).with(program).check();
}
@Test void testMinusMergeRule() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ProjectSetOpTransposeRule.INSTANCE)
.addRuleInstance(ProjectRemoveRule.INSTANCE)
.addRuleInstance(UnionMergeRule.MINUS_INSTANCE)
.build();
final String sql = "select * from (\n"
+ "select * from (\n"
+ " select name, deptno from\n"
+ " (\n"
+ " select name, deptno, count(1) from dept group by name, deptno\n"
+ " except all\n"
+ " select name, deptno, 1 from dept\n"
+ " ) subq\n"
+ " except all\n"
+ " select name, deptno from\n"
+ " (\n"
+ " select name, deptno, 1 from dept\n"
+ " except all\n"
+ " select name, deptno, count(1) from dept group by name, deptno\n"
+ " ) subq2\n"
+ ") a\n"
+ "except all\n"
+ "select name, deptno from dept\n"
+ ") aa\n";
sql(sql).with(program).check();
}
/** Tests that a filters is combined are combined if they are identical,
* even if one of them originates in an ON clause of a JOIN. */
@Test void testMergeJoinFilter() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(FilterProjectTransposeRule.INSTANCE)
.addRuleInstance(FilterMergeRule.INSTANCE)
.addRuleInstance(FilterJoinRule.FILTER_ON_JOIN)
.build();
final String sql = "select * from (\n"
+ " select d.deptno, e.ename\n"
+ " from emp as e\n"
+ " join dept as d\n"
+ " on e.deptno = d.deptno\n"
+ " and d.deptno = 10)\n"
+ "where deptno = 10\n";
sql(sql).with(program).check();
}
/** Tests {@link UnionMergeRule}, which merges 2 {@link Union} operators into
* a single {@code Union} with 3 inputs. */
@Test void testMergeUnionAll() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(UnionMergeRule.INSTANCE)
.build();
final String sql = "select * from emp where deptno = 10\n"
+ "union all\n"
+ "select * from emp where deptno = 20\n"
+ "union all\n"
+ "select * from emp where deptno = 30\n";
sql(sql).with(program).check();
}
/** Tests {@link UnionMergeRule}, which merges 2 {@link Union}
* {@code DISTINCT} (not {@code ALL}) operators into a single
* {@code Union} with 3 inputs. */
@Test void testMergeUnionDistinct() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(UnionMergeRule.INSTANCE)
.build();
final String sql = "select * from emp where deptno = 10\n"
+ "union distinct\n"
+ "select * from emp where deptno = 20\n"
+ "union\n" // same as 'union distinct'
+ "select * from emp where deptno = 30\n";
sql(sql).with(program).check();
}
/** Tests that {@link UnionMergeRule} does nothing if its arguments have
* different {@code ALL} settings. */
@Test void testMergeUnionMixed() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(UnionMergeRule.INSTANCE)
.build();
final String sql = "select * from emp where deptno = 10\n"
+ "union\n"
+ "select * from emp where deptno = 20\n"
+ "union all\n"
+ "select * from emp where deptno = 30\n";
sql(sql).with(program).checkUnchanged();
}
/** Tests that {@link UnionMergeRule} converts all inputs to DISTINCT
* if the top one is DISTINCT.
* (Since UNION is left-associative, the "top one" is the rightmost.) */
@Test void testMergeUnionMixed2() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(UnionMergeRule.INSTANCE)
.build();
final String sql = "select * from emp where deptno = 10\n"
+ "union all\n"
+ "select * from emp where deptno = 20\n"
+ "union\n"
+ "select * from emp where deptno = 30\n";
sql(sql).with(program).check();
}
/** Tests that {@link UnionMergeRule} does nothing if its arguments have
* are different set operators, {@link Union} and {@link Intersect}. */
@Test void testMergeSetOpMixed() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(UnionMergeRule.INSTANCE)
.addRuleInstance(UnionMergeRule.INTERSECT_INSTANCE)
.build();
final String sql = "select * from emp where deptno = 10\n"
+ "union\n"
+ "select * from emp where deptno = 20\n"
+ "intersect\n"
+ "select * from emp where deptno = 30\n";
sql(sql).with(program).checkUnchanged();
}
/** Tests {@link UnionMergeRule#INTERSECT_INSTANCE}, which merges 2
* {@link Intersect} operators into a single {@code Intersect} with 3
* inputs. */
@Test void testMergeIntersect() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(UnionMergeRule.INTERSECT_INSTANCE)
.build();
final String sql = "select * from emp where deptno = 10\n"
+ "intersect\n"
+ "select * from emp where deptno = 20\n"
+ "intersect\n"
+ "select * from emp where deptno = 30\n";
sql(sql).with(program).check();
}
/** Tests {@link org.apache.calcite.rel.rules.IntersectToDistinctRule},
* which rewrites an {@link Intersect} operator with 3 inputs. */
@Test void testIntersectToDistinct() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(UnionMergeRule.INTERSECT_INSTANCE)
.addRuleInstance(IntersectToDistinctRule.INSTANCE)
.build();
final String sql = "select * from emp where deptno = 10\n"
+ "intersect\n"
+ "select * from emp where deptno = 20\n"
+ "intersect\n"
+ "select * from emp where deptno = 30\n";
sql(sql).with(program).check();
}
/** Tests that {@link org.apache.calcite.rel.rules.IntersectToDistinctRule}
* correctly ignores an {@code INTERSECT ALL}. It can only handle
* {@code INTERSECT DISTINCT}. */
@Test void testIntersectToDistinctAll() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(UnionMergeRule.INTERSECT_INSTANCE)
.addRuleInstance(IntersectToDistinctRule.INSTANCE)
.build();
final String sql = "select * from emp where deptno = 10\n"
+ "intersect\n"
+ "select * from emp where deptno = 20\n"
+ "intersect all\n"
+ "select * from emp where deptno = 30\n";
sql(sql).with(program).check();
}
/** Tests {@link UnionMergeRule#MINUS_INSTANCE}, which merges 2
* {@link Minus} operators into a single {@code Minus} with 3
* inputs. */
@Test void testMergeMinus() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(UnionMergeRule.MINUS_INSTANCE)
.build();
final String sql = "select * from emp where deptno = 10\n"
+ "except\n"
+ "select * from emp where deptno = 20\n"
+ "except\n"
+ "select * from emp where deptno = 30\n";
sql(sql).with(program).check();
}
/** Tests {@link UnionMergeRule#MINUS_INSTANCE}
* does not merge {@code Minus(a, Minus(b, c))}
* into {@code Minus(a, b, c)}, which would be incorrect. */
@Test void testMergeMinusRightDeep() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(UnionMergeRule.MINUS_INSTANCE)
.build();
final String sql = "select * from emp where deptno = 10\n"
+ "except\n"
+ "select * from (\n"
+ " select * from emp where deptno = 20\n"
+ " except\n"
+ " select * from emp where deptno = 30)";
sql(sql).with(program).checkUnchanged();
}
@Test void testHeterogeneousConversion() throws Exception {
// This one tests the planner's ability to correctly
// apply different converters on top of a common
// sub-expression. The common sub-expression is the
// reference to the table sales.emps. On top of that
// are two projections, unioned at the top. For one
// of the projections, transfer it to calc, for the other,
// keep it unchanged.
HepProgram program = new HepProgramBuilder()
// Control the calc conversion.
.addMatchLimit(1)
.addRuleInstance(ProjectToCalcRule.INSTANCE)
.build();
final String sql = "select upper(ename) from emp union all\n"
+ "select lower(ename) from emp";
sql(sql).with(program).check();
}
@Test void testPushSemiJoinPastJoinRuleLeft() throws Exception {
// tests the case where the semijoin is pushed to the left
final String sql = "select e1.ename from emp e1, dept d, emp e2\n"
+ "where e1.deptno = d.deptno and e1.empno = e2.empno";
sql(sql).withRule(FilterJoinRule.FILTER_ON_JOIN,
JoinAddRedundantSemiJoinRule.INSTANCE,
SemiJoinJoinTransposeRule.INSTANCE)
.check();
}
@Test void testPushSemiJoinPastJoinRuleRight() throws Exception {
// tests the case where the semijoin is pushed to the right
final String sql = "select e1.ename from emp e1, dept d, emp e2\n"
+ "where e1.deptno = d.deptno and d.deptno = e2.deptno";
sql(sql).withRule(FilterJoinRule.FILTER_ON_JOIN,
JoinAddRedundantSemiJoinRule.INSTANCE,
SemiJoinJoinTransposeRule.INSTANCE)
.check();
}
@Test void testPushSemiJoinPastFilter() throws Exception {
final String sql = "select e.ename from emp e, dept d\n"
+ "where e.deptno = d.deptno and e.ename = 'foo'";
sql(sql).withRule(FilterJoinRule.FILTER_ON_JOIN,
JoinAddRedundantSemiJoinRule.INSTANCE,
SemiJoinFilterTransposeRule.INSTANCE)
.check();
}
@Test void testConvertMultiJoinRule() throws Exception {
final String sql = "select e1.ename from emp e1, dept d, emp e2\n"
+ "where e1.deptno = d.deptno and d.deptno = e2.deptno";
HepProgram program = new HepProgramBuilder()
.addRuleInstance(FilterJoinRule.FILTER_ON_JOIN)
.addMatchOrder(HepMatchOrder.BOTTOM_UP)
.addRuleInstance(JoinToMultiJoinRule.INSTANCE)
.build();
sql(sql).with(program).check();
}
@Test void testManyFiltersOnTopOfMultiJoinShouldCollapse() throws Exception {
HepProgram program = new HepProgramBuilder()
.addMatchOrder(HepMatchOrder.BOTTOM_UP)
.addRuleInstance(JoinToMultiJoinRule.INSTANCE)
.addRuleCollection(
Arrays.asList(FilterMultiJoinMergeRule.INSTANCE, ProjectMultiJoinMergeRule.INSTANCE))
.build();
final String sql = "select * from (select * from emp e1 left outer join dept d\n"
+ "on e1.deptno = d.deptno\n"
+ "where d.deptno > 3) where ename LIKE 'bar'";
sql(sql).with(program).check();
}
@Test void testReduceConstants() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.PROJECT_INSTANCE)
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(ReduceExpressionsRule.JOIN_INSTANCE)
.build();
// NOTE jvs 27-May-2006: among other things, this verifies
// intentionally different treatment for identical coalesce expression
// in select and where.
// There is "CAST(2 AS INTEGER)" in the plan because 2 has type "INTEGER NOT
// NULL" and we need "INTEGER".
final String sql = "select"
+ " 1+2, d.deptno+(3+4), (5+6)+d.deptno, cast(null as integer),"
+ " coalesce(2,null), row(7+8)"
+ " from dept d inner join emp e"
+ " on d.deptno = e.deptno + (5-5)"
+ " where d.deptno=(7+8) and d.deptno=(8+7) and d.deptno=coalesce(2,null)";
sql(sql).with(program)
.withProperty(Hook.REL_BUILDER_SIMPLIFY, false)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-570">[CALCITE-570]
* ReduceExpressionsRule throws "duplicate key" exception</a>. */
@Test void testReduceConstantsDup() throws Exception {
final String sql = "select d.deptno"
+ " from dept d"
+ " where d.deptno=7 and d.deptno=8";
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-935">[CALCITE-935]
* Improve how ReduceExpressionsRule handles duplicate constraints</a>. */
@Test void testReduceConstantsDup2() throws Exception {
final String sql = "select *\n"
+ "from emp\n"
+ "where deptno=7 and deptno=8\n"
+ "and empno = 10 and mgr is null and empno = 10";
sql(sql).withRule(ReduceExpressionsRule.PROJECT_INSTANCE,
ReduceExpressionsRule.FILTER_INSTANCE)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3198">[CALCITE-3198]
* Enhance RexSimplify to handle (x&lt;&gt;a or x&lt;&gt;b)</a>. */
@Test void testReduceConstantsDup3() throws Exception {
final String sql = "select d.deptno"
+ " from dept d"
+ " where d.deptno<>7 or d.deptno<>8";
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3198">[CALCITE-3198]
* Enhance RexSimplify to handle (x&lt;&gt;a or x&lt;&gt;b)</a>. */
@Test void testReduceConstantsDup3Null() throws Exception {
final String sql = "select e.empno"
+ " from emp e"
+ " where e.mgr<>7 or e.mgr<>8";
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3198">[CALCITE-3198]
* Enhance RexSimplify to handle (x&lt;&gt;a or x&lt;&gt;b)</a>. */
@Test void testReduceConstantsDupNot() throws Exception {
final String sql = "select d.deptno"
+ " from dept d"
+ " where not(d.deptno=7 and d.deptno=8)";
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3198">[CALCITE-3198]
* Enhance RexSimplify to handle (x&lt;&gt;a or x&lt;&gt;b)</a>. */
@Test void testReduceConstantsDupNotNull() throws Exception {
final String sql = "select e.empno"
+ " from emp e"
+ " where not(e.mgr=7 and e.mgr=8)";
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3198">[CALCITE-3198]
* Enhance RexSimplify to handle (x&lt;&gt;a or x&lt;&gt;b)</a>. */
@Test void testReduceConstantsDupNot2() throws Exception {
final String sql = "select d.deptno"
+ " from dept d"
+ " where not(d.deptno=7 and d.name='foo' and d.deptno=8)";
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3198">[CALCITE-3198]
* Enhance RexSimplify to handle (x&lt;&gt;a or x&lt;&gt;b)</a>. */
@Test void testReduceConstantsDupNot2Null() throws Exception {
final String sql = "select e.empno"
+ " from emp e"
+ " where not(e.mgr=7 and e.deptno=8 and e.mgr=8)";
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE).check();
}
@Test void testPullNull() throws Exception {
final String sql = "select *\n"
+ "from emp\n"
+ "where deptno=7\n"
+ "and empno = 10 and mgr is null and empno = 10";
sql(sql).withRule(ReduceExpressionsRule.PROJECT_INSTANCE,
ReduceExpressionsRule.FILTER_INSTANCE,
ReduceExpressionsRule.JOIN_INSTANCE)
.check();
}
@Test void testOrAlwaysTrue() {
final String sql = "select * from EMPNULLABLES_20\n"
+ "where sal is null or sal is not null";
sql(sql).withRule(ReduceExpressionsRule.PROJECT_INSTANCE,
ReduceExpressionsRule.FILTER_INSTANCE,
ReduceExpressionsRule.JOIN_INSTANCE)
.check();
}
@Test void testOrAlwaysTrue2() {
final String sql = "select * from EMPNULLABLES_20\n"
+ "where sal is not null or sal is null";
sql(sql).withRule(ReduceExpressionsRule.PROJECT_INSTANCE,
ReduceExpressionsRule.FILTER_INSTANCE,
ReduceExpressionsRule.JOIN_INSTANCE)
.check();
}
@Test void testReduceConstants2() throws Exception {
final String sql = "select p1 is not distinct from p0\n"
+ "from (values (2, cast(null as integer))) as t(p0, p1)";
sql(sql).withRule(ReduceExpressionsRule.PROJECT_INSTANCE,
ReduceExpressionsRule.FILTER_INSTANCE,
ReduceExpressionsRule.JOIN_INSTANCE)
.checkUnchanged();
}
@Test void testReduceConstants3() throws Exception {
final String sql = "select e.mgr is not distinct from f.mgr "
+ "from emp e join emp f on (e.mgr=f.mgr) where e.mgr is null";
sql(sql).withRule(ReduceExpressionsRule.PROJECT_INSTANCE,
ReduceExpressionsRule.FILTER_INSTANCE,
ReduceExpressionsRule.JOIN_INSTANCE)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-902">[CALCITE-902]
* Match nullability when reducing expressions in a Project</a>. */
@Test void testReduceConstantsProjectNullable() throws Exception {
final String sql = "select mgr from emp where mgr=10";
sql(sql).withRule(ReduceExpressionsRule.PROJECT_INSTANCE,
ReduceExpressionsRule.FILTER_INSTANCE,
ReduceExpressionsRule.JOIN_INSTANCE)
.check();
}
// see HIVE-9645
@Test void testReduceConstantsNullEqualsOne() throws Exception {
final String sql = "select count(1) from emp where cast(null as integer) = 1";
sql(sql).withRule(ReduceExpressionsRule.PROJECT_INSTANCE,
ReduceExpressionsRule.FILTER_INSTANCE,
ReduceExpressionsRule.JOIN_INSTANCE)
.check();
}
// see HIVE-9644
@Test void testReduceConstantsCaseEquals() throws Exception {
final String sql = "select count(1) from emp\n"
+ "where case deptno\n"
+ " when 20 then 2\n"
+ " when 10 then 1\n"
+ " else 3 end = 1";
// Equivalent to 'deptno = 10'
sql(sql).withRule(ReduceExpressionsRule.PROJECT_INSTANCE,
ReduceExpressionsRule.FILTER_INSTANCE,
ReduceExpressionsRule.JOIN_INSTANCE)
.check();
}
@Test void testReduceConstantsCaseEquals2() throws Exception {
final String sql = "select count(1) from emp\n"
+ "where case deptno\n"
+ " when 20 then 2\n"
+ " when 10 then 1\n"
+ " else cast(null as integer) end = 1";
// Equivalent to 'case when deptno = 20 then false
// when deptno = 10 then true
// else null end'
sql(sql).withRule(ReduceExpressionsRule.PROJECT_INSTANCE,
ReduceExpressionsRule.FILTER_INSTANCE,
ReduceExpressionsRule.JOIN_INSTANCE)
.check();
}
@Test void testReduceConstantsCaseEquals3() throws Exception {
final String sql = "select count(1) from emp\n"
+ "where case deptno\n"
+ " when 30 then 1\n"
+ " when 20 then 2\n"
+ " when 10 then 1\n"
+ " when 30 then 111\n"
+ " else 0 end = 1";
// Equivalent to 'deptno = 30 or deptno = 10'
sql(sql).withRule(ReduceExpressionsRule.PROJECT_INSTANCE,
ReduceExpressionsRule.FILTER_INSTANCE,
ReduceExpressionsRule.JOIN_INSTANCE)
.check();
}
@Test void testSkipReduceConstantsCaseEquals() throws Exception {
final String sql = "select * from emp e1, emp e2\n"
+ "where coalesce(e1.mgr, -1) = coalesce(e2.mgr, -1)";
sql(sql).withRule(ReduceExpressionsRule.PROJECT_INSTANCE,
ReduceExpressionsRule.FILTER_INSTANCE,
FilterJoinRule.FilterIntoJoinRule.FILTER_ON_JOIN)
.check();
}
@Test void testReduceConstantsEliminatesFilter() throws Exception {
final String sql = "select * from (values (1,2)) where 1 + 2 > 3 + CAST(NULL AS INTEGER)";
// WHERE NULL is the same as WHERE FALSE, so get empty result
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1860">[CALCITE-1860]
* Duplicate null predicates cause NullPointerException in RexUtil</a>. */
@Test void testReduceConstantsNull() throws Exception {
final String sql = "select * from (\n"
+ " select *\n"
+ " from (\n"
+ " select cast(null as integer) as n\n"
+ " from emp)\n"
+ " where n is null and n is null)\n"
+ "where n is null";
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-566">[CALCITE-566]
* ReduceExpressionsRule requires planner to have an Executor</a>. */
@Test void testReduceConstantsRequiresExecutor() throws Exception {
// Remove the executor
tester.convertSqlToRel("values 1").rel.getCluster().getPlanner()
.setExecutor(null);
// Rule should not fire, but there should be no NPE
final String sql =
"select * from (values (1,2)) where 1 + 2 > 3 + CAST(NULL AS INTEGER)";
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE).check();
}
@Test void testAlreadyFalseEliminatesFilter() throws Exception {
final String sql = "select * from (values (1,2)) where false";
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE).check();
}
@Test void testReduceConstantsCalc() throws Exception {
// This reduction does not work using
// ReduceExpressionsRule.PROJECT_INSTANCE or FILTER_INSTANCE,
// only CALC_INSTANCE, because we need to pull the project expression
// upper('table')
// into the condition
// upper('table') = 'TABLE'
// and reduce it to TRUE. Only in the Calc are projects and conditions
// combined.
HepProgram program = new HepProgramBuilder()
.addRuleInstance(FilterProjectTransposeRule.INSTANCE)
.addRuleInstance(FilterSetOpTransposeRule.INSTANCE)
.addRuleInstance(FilterToCalcRule.INSTANCE)
.addRuleInstance(ProjectToCalcRule.INSTANCE)
.addRuleInstance(CalcMergeRule.INSTANCE)
.addRuleInstance(ReduceExpressionsRule.CALC_INSTANCE)
// the hard part is done... a few more rule calls to clean up
.addRuleInstance(PruneEmptyRules.UNION_INSTANCE)
.addRuleInstance(ProjectToCalcRule.INSTANCE)
.addRuleInstance(CalcMergeRule.INSTANCE)
.addRuleInstance(ReduceExpressionsRule.CALC_INSTANCE)
.build();
// Result should be same as typing
// SELECT * FROM (VALUES ('TABLE ', 'T')) AS T(U, S)
final String sql = "select * from (\n"
+ " select upper(substring(x FROM 1 FOR 2) || substring(x FROM 3)) as u,\n"
+ " substring(x FROM 1 FOR 1) as s\n"
+ " from (\n"
+ " select 'table' as x from (values (true))\n"
+ " union\n"
+ " select 'view' from (values (true))\n"
+ " union\n"
+ " select 'foreign table' from (values (true))\n"
+ " )\n"
+ ") where u = 'TABLE'";
sql(sql).with(program).check();
}
@Test void testRemoveSemiJoin() throws Exception {
final String sql = "select e.ename from emp e, dept d\n"
+ "where e.deptno = d.deptno";
sql(sql).withRule(FilterJoinRule.FILTER_ON_JOIN,
JoinAddRedundantSemiJoinRule.INSTANCE,
SemiJoinRemoveRule.INSTANCE)
.check();
}
@Test void testRemoveSemiJoinWithFilter() throws Exception {
final String sql = "select e.ename from emp e, dept d\n"
+ "where e.deptno = d.deptno and e.ename = 'foo'";
sql(sql).withRule(FilterJoinRule.FILTER_ON_JOIN,
JoinAddRedundantSemiJoinRule.INSTANCE,
SemiJoinFilterTransposeRule.INSTANCE,
SemiJoinRemoveRule.INSTANCE)
.check();
}
@Test void testRemoveSemiJoinRight() throws Exception {
final String sql = "select e1.ename from emp e1, dept d, emp e2\n"
+ "where e1.deptno = d.deptno and d.deptno = e2.deptno";
sql(sql).withRule(FilterJoinRule.FILTER_ON_JOIN,
JoinAddRedundantSemiJoinRule.INSTANCE,
SemiJoinJoinTransposeRule.INSTANCE,
SemiJoinRemoveRule.INSTANCE)
.check();
}
@Test void testRemoveSemiJoinRightWithFilter() throws Exception {
final String sql = "select e1.ename from emp e1, dept d, emp e2\n"
+ "where e1.deptno = d.deptno and d.deptno = e2.deptno\n"
+ "and d.name = 'foo'";
sql(sql).withRule(FilterJoinRule.FILTER_ON_JOIN,
JoinAddRedundantSemiJoinRule.INSTANCE,
SemiJoinJoinTransposeRule.INSTANCE,
SemiJoinFilterTransposeRule.INSTANCE,
SemiJoinRemoveRule.INSTANCE)
.check();
}
private void checkPlanning(String query) throws Exception {
final Tester tester1 = tester.withCatalogReaderFactory(
(typeFactory, caseSensitive) -> new MockCatalogReader(typeFactory, caseSensitive) {
@Override public MockCatalogReader init() {
// CREATE SCHEMA abc;
// CREATE TABLE a(a INT);
// ...
// CREATE TABLE j(j INT);
MockSchema schema = new MockSchema("SALES");
registerSchema(schema);
final RelDataType intType =
typeFactory.createSqlType(SqlTypeName.INTEGER);
for (int i = 0; i < 10; i++) {
String t = String.valueOf((char) ('A' + i));
MockTable table = MockTable.create(this, schema, t, false, 100);
table.addColumn(t, intType);
registerTable(table);
}
return this;
}
// CHECKSTYLE: IGNORE 1
});
HepProgram program = new HepProgramBuilder()
.addMatchOrder(HepMatchOrder.BOTTOM_UP)
.addRuleInstance(ProjectRemoveRule.INSTANCE)
.addRuleInstance(JoinToMultiJoinRule.INSTANCE)
.build();
sql(query).withTester(t -> tester1)
.with(program)
.check();
}
@Test void testConvertMultiJoinRuleOuterJoins() throws Exception {
checkPlanning("select * from "
+ " (select * from "
+ " (select * from "
+ " (select * from A right outer join B on a = b) "
+ " left outer join "
+ " (select * from C full outer join D on c = d)"
+ " on a = c and b = d) "
+ " right outer join "
+ " (select * from "
+ " (select * from E full outer join F on e = f) "
+ " right outer join "
+ " (select * from G left outer join H on g = h) "
+ " on e = g and f = h) "
+ " on a = e and b = f and c = g and d = h) "
+ " inner join "
+ " (select * from I inner join J on i = j) "
+ " on a = i and h = j");
}
@Test void testConvertMultiJoinRuleOuterJoins2() throws Exception {
// in (A right join B) join C, pushing C is not allowed;
// therefore there should be 2 MultiJoin
checkPlanning("select * from A right join B on a = b join C on b = c");
}
@Test void testConvertMultiJoinRuleOuterJoins3() throws Exception {
// in (A join B) left join C, pushing C is allowed;
// therefore there should be 1 MultiJoin
checkPlanning("select * from A join B on a = b left join C on b = c");
}
@Test void testConvertMultiJoinRuleOuterJoins4() throws Exception {
// in (A join B) right join C, pushing C is not allowed;
// therefore there should be 2 MultiJoin
checkPlanning("select * from A join B on a = b right join C on b = c");
}
@Test void testPushSemiJoinPastProject() throws Exception {
final String sql = "select e.* from\n"
+ "(select ename, trim(job), sal * 2, deptno from emp) e, dept d\n"
+ "where e.deptno = d.deptno";
sql(sql).withRule(FilterJoinRule.FILTER_ON_JOIN,
JoinAddRedundantSemiJoinRule.INSTANCE,
SemiJoinProjectTransposeRule.INSTANCE)
.check();
}
@Test void testReduceValuesUnderFilter() throws Exception {
// Plan should be same as for
// select a, b from (values (10,'x')) as t(a, b)");
final String sql = "select a, b from (values (10, 'x'), (20, 'y')) as t(a, b) where a < 15";
sql(sql).withRule(FilterProjectTransposeRule.INSTANCE,
ValuesReduceRule.FILTER_INSTANCE)
.check();
}
@Test void testReduceValuesUnderProject() throws Exception {
// Plan should be same as for
// select a, b as x from (values (11), (23)) as t(x)");
final String sql = "select a + b from (values (10, 1), (20, 3)) as t(a, b)";
sql(sql).withRule(ProjectMergeRule.INSTANCE,
ValuesReduceRule.PROJECT_INSTANCE)
.check();
}
@Test void testReduceValuesUnderProjectFilter() throws Exception {
// Plan should be same as for
// select * from (values (11, 1, 10), (23, 3, 20)) as t(x, b, a)");
final String sql = "select a + b as x, b, a\n"
+ "from (values (10, 1), (30, 7), (20, 3)) as t(a, b)\n"
+ "where a - b < 21";
sql(sql).withRule(FilterProjectTransposeRule.INSTANCE,
ProjectMergeRule.INSTANCE,
ValuesReduceRule.PROJECT_FILTER_INSTANCE)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1439">[CALCITE-1439]
* Handling errors during constant reduction</a>. */
@Test void testReduceCase() throws Exception {
final String sql = "select\n"
+ " case when false then cast(2.1 as float)\n"
+ " else cast(1 as integer) end as newcol\n"
+ "from emp";
sql(sql).withRule(ReduceExpressionsRule.PROJECT_INSTANCE)
.withProperty(Hook.REL_BUILDER_SIMPLIFY, false)
.check();
}
private void checkReduceNullableToNotNull(ReduceExpressionsRule rule) {
final String sql = "select\n"
+ " empno + case when 'a' = 'a' then 1 else null end as newcol\n"
+ "from emp";
sql(sql).withRule(rule)
.withProperty(Hook.REL_BUILDER_SIMPLIFY, false)
.check();
}
/** Test case that reduces a nullable expression to a NOT NULL literal that
* is cast to nullable. */
@Test void testReduceNullableToNotNull() throws Exception {
checkReduceNullableToNotNull(ReduceExpressionsRule.PROJECT_INSTANCE);
}
/** Test case that reduces a nullable expression to a NOT NULL literal. */
@Test void testReduceNullableToNotNull2() throws Exception {
final ReduceExpressionsRule.ProjectReduceExpressionsRule rule =
new ReduceExpressionsRule.ProjectReduceExpressionsRule(
LogicalProject.class, false,
RelFactories.LOGICAL_BUILDER);
checkReduceNullableToNotNull(rule);
}
@Test void testReduceConstantsIsNull() throws Exception {
final String sql = "select empno from emp where empno=10 and empno is null";
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE).check();
}
@Test void testReduceConstantsIsNotNull() throws Exception {
final String sql = "select empno from emp\n"
+ "where empno=10 and empno is not null";
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE).check();
}
@Test void testReduceConstantsNegated() throws Exception {
final String sql = "select empno from emp\n"
+ "where empno=10 and not(empno=10)";
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE).check();
}
@Test void testReduceConstantsNegatedInverted() throws Exception {
final String sql = "select empno from emp where empno>10 and empno<=10";
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2638">[CALCITE-2638]
* Constant reducer must not duplicate calls to non-deterministic
* functions</a>. */
@Test void testReduceConstantsNonDeterministicFunction() {
final DiffRepository diffRepos = getDiffRepos();
final SqlOperator nonDeterministicOp =
new SqlSpecialOperator("NDC", SqlKind.OTHER_FUNCTION, 0, false,
ReturnTypes.INTEGER, null, null) {
@Override public boolean isDeterministic() {
return false;
}
};
// Build a tree equivalent to the SQL
// SELECT sal, n
// FROM (SELECT sal, NDC() AS n FROM emp)
// WHERE n > 10
final RelBuilder builder =
RelBuilder.create(RelBuilderTest.config().build());
final RelNode root =
builder.scan("EMP")
.project(builder.field("SAL"),
builder.alias(builder.call(nonDeterministicOp), "N"))
.filter(
builder.call(SqlStdOperatorTable.GREATER_THAN,
builder.field("N"), builder.literal(10)))
.build();
HepProgram preProgram = new HepProgramBuilder().build();
HepPlanner prePlanner = new HepPlanner(preProgram);
prePlanner.setRoot(root);
final RelNode relBefore = prePlanner.findBestExp();
final String planBefore = NL + RelOptUtil.toString(relBefore);
diffRepos.assertEquals("planBefore", "${planBefore}", planBefore);
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(ReduceExpressionsRule.PROJECT_INSTANCE)
.build();
HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(root);
final RelNode relAfter = hepPlanner.findBestExp();
final String planAfter = NL + RelOptUtil.toString(relAfter);
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
}
/** Checks that constant reducer duplicates calls to dynamic functions, if
* appropriate. CURRENT_TIMESTAMP is a dynamic function. */
@Test void testReduceConstantsDynamicFunction() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(ReduceExpressionsRule.PROJECT_INSTANCE)
.build();
final String sql = "select sal, t\n"
+ "from (select sal, current_timestamp t from emp)\n"
+ "where t > TIMESTAMP '2018-01-01 00:00:00'";
sql(sql).with(program).checkUnchanged();
}
@Test void testCasePushIsAlwaysWorking() throws Exception {
final String sql = "select empno from emp"
+ " where case when sal > 1000 then empno else sal end = 1";
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE,
ReduceExpressionsRule.CALC_INSTANCE,
ReduceExpressionsRule.PROJECT_INSTANCE)
.check();
}
@Test void testReduceValuesNull() throws Exception {
// The NULL literal presents pitfalls for value-reduction. Only
// an INSERT statement contains un-CASTed NULL values.
final String sql = "insert into EMPNULLABLES(EMPNO, ENAME, JOB) (select 0, 'null', NULL)";
sql(sql).withRule(ValuesReduceRule.PROJECT_INSTANCE).check();
}
@Test void testReduceValuesToEmpty() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(FilterProjectTransposeRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.addRuleInstance(ValuesReduceRule.PROJECT_FILTER_INSTANCE)
.build();
// Plan should be same as for
// select * from (values (11, 1, 10), (23, 3, 20)) as t(x, b, a)");
final String sql = "select a + b as x, b, a from (values (10, 1), (30, 7)) as t(a, b)\n"
+ "where a - b < 0";
sql(sql).with(program).check();
}
@Test void testReduceConstantsWindow() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ProjectToWindowRule.PROJECT)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.addRuleInstance(ProjectWindowTransposeRule.INSTANCE)
.addRuleInstance(ReduceExpressionsRule.WINDOW_INSTANCE)
.build();
final String sql = "select col1, col2, col3\n"
+ "from (\n"
+ " select empno,\n"
+ " sum(100) over (partition by deptno, sal order by sal) as col1,\n"
+ " sum(100) over (partition by sal order by deptno) as col2,\n"
+ " sum(sal) over (partition by deptno order by sal) as col3\n"
+ " from emp where sal = 5000)";
sql(sql).with(program).check();
}
@Test void testEmptyFilterProjectUnion() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(FilterSetOpTransposeRule.INSTANCE)
.addRuleInstance(FilterProjectTransposeRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.addRuleInstance(ValuesReduceRule.PROJECT_FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.UNION_INSTANCE)
.build();
// Plan should be same as for
// select * from (values (30, 3)) as t(x, y)");
final String sql = "select * from (\n"
+ "select * from (values (10, 1), (30, 3)) as t (x, y)\n"
+ "union all\n"
+ "select * from (values (20, 2))\n"
+ ")\n"
+ "where x + y > 30";
sql(sql).with(program).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1488">[CALCITE-1488]
* ValuesReduceRule should ignore empty Values</a>. */
@Test void testEmptyProject() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ValuesReduceRule.PROJECT_FILTER_INSTANCE)
.addRuleInstance(ValuesReduceRule.FILTER_INSTANCE)
.addRuleInstance(ValuesReduceRule.PROJECT_INSTANCE)
.build();
final String sql = "select z + x from (\n"
+ " select x + y as z, x from (\n"
+ " select * from (values (10, 1), (30, 3)) as t (x, y)\n"
+ " where x + y > 50))";
sql(sql).with(program).check();
}
/** Same query as {@link #testEmptyProject()}, and {@link PruneEmptyRules}
* is able to do the job that {@link ValuesReduceRule} cannot do. */
@Test void testEmptyProject2() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ValuesReduceRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.build();
final String sql = "select z + x from (\n"
+ " select x + y as z, x from (\n"
+ " select * from (values (10, 1), (30, 3)) as t (x, y)\n"
+ " where x + y > 50))";
sql(sql).with(program).check();
}
@Test void testEmptyIntersect() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ValuesReduceRule.PROJECT_FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.INTERSECT_INSTANCE)
.build();
final String sql = "select * from (values (30, 3))"
+ "intersect\n"
+ "select *\nfrom (values (10, 1), (30, 3)) as t (x, y) where x > 50\n"
+ "intersect\n"
+ "select * from (values (30, 3))";
sql(sql).with(program).check();
}
@Test void testEmptyMinus() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ValuesReduceRule.PROJECT_FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.MINUS_INSTANCE)
.build();
// First input is empty; therefore whole expression is empty
final String sql = "select * from (values (30, 3)) as t (x, y)\n"
+ "where x > 30\n"
+ "except\n"
+ "select * from (values (20, 2))\n"
+ "except\n"
+ "select * from (values (40, 4))";
sql(sql).with(program).check();
}
@Test void testEmptyMinus2() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ValuesReduceRule.PROJECT_FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.MINUS_INSTANCE)
.build();
// Second and fourth inputs are empty; they are removed
final String sql = "select * from (values (30, 3)) as t (x, y)\n"
+ "except\n"
+ "select * from (values (20, 2)) as t (x, y) where x > 30\n"
+ "except\n"
+ "select * from (values (40, 4))\n"
+ "except\n"
+ "select * from (values (50, 5)) as t (x, y) where x > 50";
sql(sql).with(program).check();
}
@Test void testLeftEmptyInnerJoin() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();
// Plan should be empty
final String sql = "select * from (\n"
+ "select * from emp where false) as e\n"
+ "join dept as d on e.deptno = d.deptno";
sql(sql).with(program).check();
}
@Test void testLeftEmptyLeftJoin() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();
// Plan should be empty
final String sql = "select * from (\n"
+ " select * from emp where false) e\n"
+ "left join dept d on e.deptno = d.deptno";
sql(sql).with(program).check();
}
@Test void testLeftEmptyRightJoin() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();
// Plan should be equivalent to "select * from emp right join dept".
// Cannot optimize away the join because of RIGHT.
final String sql = "select * from (\n"
+ " select * from emp where false) e\n"
+ "right join dept d on e.deptno = d.deptno";
sql(sql).with(program).check();
}
@Test void testLeftEmptyFullJoin() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();
// Plan should be equivalent to "select * from emp full join dept".
// Cannot optimize away the join because of FULL.
final String sql = "select * from (\n"
+ " select * from emp where false) e\n"
+ "full join dept d on e.deptno = d.deptno";
sql(sql).with(program).check();
}
@Test void testLeftEmptySemiJoin() {
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
final RelNode relNode = relBuilder
.scan("EMP").empty()
.scan("DEPT")
.semiJoin(relBuilder
.equals(
relBuilder.field(2, 0, "DEPTNO"),
relBuilder.field(2, 1, "DEPTNO")))
.project(relBuilder.field("EMPNO"))
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();
final HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
final RelNode output = hepPlanner.findBestExp();
final String planBefore = NL + RelOptUtil.toString(relNode);
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planBefore", "${planBefore}", planBefore);
// Plan should be empty
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
}
@Test void testLeftEmptyAntiJoin() {
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
final RelNode relNode = relBuilder
.scan("EMP").empty()
.scan("DEPT")
.antiJoin(relBuilder
.equals(
relBuilder.field(2, 0, "DEPTNO"),
relBuilder.field(2, 1, "DEPTNO")))
.project(relBuilder.field("EMPNO"))
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();
final HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
final RelNode output = hepPlanner.findBestExp();
final String planBefore = NL + RelOptUtil.toString(relNode);
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planBefore", "${planBefore}", planBefore);
// Plan should be empty
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
}
@Test void testRightEmptyInnerJoin() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();
// Plan should be empty
final String sql = "select * from emp e\n"
+ "join (select * from dept where false) as d\n"
+ "on e.deptno = d.deptno";
sql(sql).with(program).check();
}
@Test void testRightEmptyLeftJoin() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();
// Plan should be equivalent to "select * from emp left join dept".
// Cannot optimize away the join because of LEFT.
final String sql = "select * from emp e\n"
+ "left join (select * from dept where false) as d\n"
+ "on e.deptno = d.deptno";
sql(sql).with(program).check();
}
@Test void testRightEmptyRightJoin() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();
// Plan should be empty
final String sql = "select * from emp e\n"
+ "right join (select * from dept where false) as d\n"
+ "on e.deptno = d.deptno";
sql(sql).with(program).check();
}
@Test void testRightEmptyFullJoin() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();
// Plan should be equivalent to "select * from emp full join dept".
// Cannot optimize away the join because of FULL.
final String sql = "select * from emp e\n"
+ "full join (select * from dept where false) as d\n"
+ "on e.deptno = d.deptno";
sql(sql).with(program).check();
}
@Test void testRightEmptySemiJoin() {
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
final RelNode relNode = relBuilder
.scan("EMP")
.scan("DEPT").empty()
.semiJoin(relBuilder
.equals(
relBuilder.field(2, 0, "DEPTNO"),
relBuilder.field(2, 1, "DEPTNO")))
.project(relBuilder.field("EMPNO"))
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();
final HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
final RelNode output = hepPlanner.findBestExp();
final String planBefore = NL + RelOptUtil.toString(relNode);
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planBefore", "${planBefore}", planBefore);
// Plan should be empty
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
}
@Test void testRightEmptyAntiJoin() {
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
final RelNode relNode = relBuilder
.scan("EMP")
.scan("DEPT").empty()
.antiJoin(relBuilder
.equals(
relBuilder.field(2, 0, "DEPTNO"),
relBuilder.field(2, 1, "DEPTNO")))
.project(relBuilder.field("EMPNO"))
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();
final HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
final RelNode output = hepPlanner.findBestExp();
final String planBefore = NL + RelOptUtil.toString(relNode);
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planBefore", "${planBefore}", planBefore);
// Plan should be scan("EMP") (i.e. join's left child)
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
}
@Test void testRightEmptyAntiJoinNonEqui() {
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
final RelNode relNode = relBuilder
.scan("EMP")
.scan("DEPT").empty()
.antiJoin(relBuilder
.equals(
relBuilder.field(2, 0, "DEPTNO"),
relBuilder.field(2, 1, "DEPTNO")),
relBuilder
.equals(
relBuilder.field(2, 0, "SAL"),
relBuilder.literal(2000)))
.project(relBuilder.field("EMPNO"))
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();
final HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
final RelNode output = hepPlanner.findBestExp();
final String planBefore = NL + RelOptUtil.toString(relNode);
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planBefore", "${planBefore}", planBefore);
// Plan should be scan("EMP") (i.e. join's left child)
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
}
@Test void testEmptySort() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.SORT_INSTANCE)
.build();
final String sql = "select * from emp where false order by deptno";
sql(sql).with(program).check();
}
@Test void testEmptySort2() {
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
final RelNode relNode = relBuilder
.scan("DEPT").empty()
.sort(
relBuilder.field("DNAME"),
relBuilder.field("DEPTNO"))
.build();
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(PruneEmptyRules.SORT_INSTANCE)
.build();
final HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
final RelNode output = hepPlanner.findBestExp();
final String planBefore = NL + RelOptUtil.toString(relNode);
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planBefore", "${planBefore}", planBefore);
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
}
@Test void testEmptySortLimitZero() {
final String sql = "select * from emp order by deptno limit 0";
sql(sql).withRule(PruneEmptyRules.SORT_FETCH_ZERO_INSTANCE).check();
}
@Test void testEmptyAggregate() {
HepProgram preProgram = HepProgram.builder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.AGGREGATE_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.build();
final String sql = "select sum(empno) from emp where false group by deptno";
sql(sql).withPre(preProgram).with(program).check();
}
@Test void testEmptyAggregateEmptyKey() {
HepProgram preProgram = HepProgram.builder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(PruneEmptyRules.AGGREGATE_INSTANCE)
.build();
final String sql = "select sum(empno) from emp where false";
sql(sql).withPre(preProgram)
.with(program)
.checkUnchanged();
}
@Test void testEmptyAggregateEmptyKeyWithAggregateValuesRule() {
HepProgram preProgram = HepProgram
.builder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateValuesRule.INSTANCE)
.build();
final String sql = "select count(*), sum(empno) from emp where false";
sql(sql).withPre(preProgram).with(program).check();
}
@Test void testReduceCasts() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.PROJECT_INSTANCE)
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(ReduceExpressionsRule.JOIN_INSTANCE)
.build();
// The resulting plan should have no cast expressions
final String sql = "select cast(d.name as varchar(128)), cast(e.empno as integer)\n"
+ "from dept as d inner join emp as e\n"
+ "on cast(d.deptno as integer) = cast(e.deptno as integer)\n"
+ "where cast(e.job as varchar(1)) = 'Manager'";
sql(sql).with(program)
.checkUnchanged();
}
/** Tests that a cast from a TIME to a TIMESTAMP is not reduced. It is not
* constant because the result depends upon the current date. */
@Test void testReduceCastTimeUnchanged() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.PROJECT_INSTANCE)
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(ReduceExpressionsRule.JOIN_INSTANCE)
.build();
sql("select cast(time '12:34:56' as timestamp) from emp as e")
.with(program)
.checkUnchanged();
}
@Test void testReduceCastAndConsts() throws Exception {
// Make sure constant expressions inside the cast can be reduced
// in addition to the casts.
final String sql = "select * from emp\n"
+ "where cast((empno + (10/2)) as int) = 13";
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE).check();
}
@Test public <T> void testReduceCaseNullabilityChange() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(ReduceExpressionsRule.PROJECT_INSTANCE)
.build();
final String sql = "select case when empno = 1 then 1\n"
+ "when 1 IS NOT NULL then 2\n"
+ "else null end as qx "
+ "from emp";
sql(sql)
.withProperty(Hook.REL_BUILDER_SIMPLIFY, false)
.with(program).check();
}
@Test void testReduceCastsNullable() throws Exception {
HepProgram program = new HepProgramBuilder()
// Simulate the way INSERT will insert casts to the target types
.addRuleInstance(
new CoerceInputsRule(LogicalTableModify.class, false,
RelFactories.LOGICAL_BUILDER))
// Convert projects to calcs, merge two calcs, and then
// reduce redundant casts in merged calc.
.addRuleInstance(ProjectToCalcRule.INSTANCE)
.addRuleInstance(CalcMergeRule.INSTANCE)
.addRuleInstance(ReduceExpressionsRule.CALC_INSTANCE)
.build();
final String sql = "insert into sales.dept(deptno, name)\n"
+ "select empno, cast(job as varchar(128)) from sales.empnullables";
sql(sql).with(program).check();
}
@Test void testReduceCaseWhenWithCast() {
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
final RexBuilder rexBuilder = relBuilder.getRexBuilder();
final RelDataType type = rexBuilder.getTypeFactory().createSqlType(SqlTypeName.BIGINT);
RelNode left = relBuilder
.values(new String[]{"x", "y"}, 1, 2).build();
RexNode ref = rexBuilder.makeInputRef(left, 0);
RexNode literal1 = rexBuilder.makeLiteral(1, type, false);
RexNode literal2 = rexBuilder.makeLiteral(2, type, false);
RexNode literal3 = rexBuilder.makeLiteral(3, type, false);
// CASE WHEN x % 2 = 1 THEN x < 2
// WHEN x % 3 = 2 THEN x < 1
// ELSE x < 3
final RexNode caseRexNode = rexBuilder.makeCall(SqlStdOperatorTable.CASE,
rexBuilder.makeCall(SqlStdOperatorTable.EQUALS,
rexBuilder.makeCall(SqlStdOperatorTable.MOD, ref, literal2), literal1),
rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN, ref, literal2),
rexBuilder.makeCall(SqlStdOperatorTable.EQUALS,
rexBuilder.makeCall(SqlStdOperatorTable.MOD, ref, literal3), literal2),
rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN, ref, literal1),
rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN, ref, literal3));
final RexNode castNode = rexBuilder.makeCast(rexBuilder.getTypeFactory().
createTypeWithNullability(caseRexNode.getType(), true), caseRexNode);
final RelNode root = relBuilder
.push(left)
.project(castNode)
.build();
HepProgramBuilder builder = new HepProgramBuilder();
builder.addRuleClass(ReduceExpressionsRule.class);
HepPlanner hepPlanner = new HepPlanner(builder.build());
hepPlanner.addRule(ReduceExpressionsRule.PROJECT_INSTANCE);
hepPlanner.setRoot(root);
RelNode output = hepPlanner.findBestExp();
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
SqlToRelTestBase.assertValid(output);
}
private void basePushAggThroughUnion() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ProjectSetOpTransposeRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateUnionTransposeRule.INSTANCE)
.build();
sql("${sql}").with(program).check();
}
@Test void testPushSumConstantThroughUnion() throws Exception {
basePushAggThroughUnion();
}
@Test void testPushSumNullConstantThroughUnion() throws Exception {
basePushAggThroughUnion();
}
@Test void testPushSumNullableThroughUnion() throws Exception {
basePushAggThroughUnion();
}
@Test void testPushSumNullableNOGBYThroughUnion() throws
Exception {
basePushAggThroughUnion();
}
@Test void testPushCountStarThroughUnion() throws Exception {
basePushAggThroughUnion();
}
@Test void testPushCountNullableThroughUnion() throws Exception {
basePushAggThroughUnion();
}
@Test void testPushMaxNullableThroughUnion() throws Exception {
basePushAggThroughUnion();
}
@Test void testPushMinThroughUnion() throws Exception {
basePushAggThroughUnion();
}
@Test void testPushAvgThroughUnion() throws Exception {
basePushAggThroughUnion();
}
@Test void testPushSumCountStarThroughUnion() throws Exception {
basePushAggThroughUnion();
}
@Test void testPushSumConstantGroupingSetsThroughUnion() throws
Exception {
basePushAggThroughUnion();
}
@Test void testPushSumNullConstantGroupingSetsThroughUnion() throws
Exception {
basePushAggThroughUnion();
}
@Test void testPushSumNullableGroupingSetsThroughUnion() throws
Exception {
basePushAggThroughUnion();
}
@Test void testPushCountStarGroupingSetsThroughUnion() throws
Exception {
basePushAggThroughUnion();
}
@Test void testPushCountNullableGroupingSetsThroughUnion() throws
Exception {
basePushAggThroughUnion();
}
@Test void testPushMaxNullableGroupingSetsThroughUnion() throws
Exception {
basePushAggThroughUnion();
}
@Test void testPushMinGroupingSetsThroughUnion() throws Exception {
basePushAggThroughUnion();
}
@Test void testPushAvgGroupingSetsThroughUnion() throws Exception {
basePushAggThroughUnion();
}
@Test void testPushSumCountStarGroupingSetsThroughUnion() throws
Exception {
basePushAggThroughUnion();
}
@Test void testPushCountFilterThroughUnion() throws Exception {
basePushAggThroughUnion();
}
@Test void testPullFilterThroughAggregate() throws Exception {
HepProgram preProgram = HepProgram.builder()
.addRuleInstance(ProjectMergeRule.INSTANCE)
.addRuleInstance(ProjectFilterTransposeRule.INSTANCE)
.build();
HepProgram program = HepProgram.builder()
.addRuleInstance(AggregateFilterTransposeRule.INSTANCE)
.build();
final String sql = "select ename, sal, deptno from ("
+ " select ename, sal, deptno"
+ " from emp"
+ " where sal > 5000)"
+ "group by ename, sal, deptno";
sql(sql).withPre(preProgram).with(program).check();
}
@Test void testPullFilterThroughAggregateGroupingSets()
throws Exception {
HepProgram preProgram = HepProgram.builder()
.addRuleInstance(ProjectMergeRule.INSTANCE)
.addRuleInstance(ProjectFilterTransposeRule.INSTANCE)
.build();
HepProgram program = HepProgram.builder()
.addRuleInstance(AggregateFilterTransposeRule.INSTANCE)
.build();
final String sql = "select ename, sal, deptno from ("
+ " select ename, sal, deptno"
+ " from emp"
+ " where sal > 5000)"
+ "group by rollup(ename, sal, deptno)";
sql(sql).withPre(preProgram).with(program).check();
}
private void basePullConstantTroughAggregate() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateProjectPullUpConstantsRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
sql("${sql}").with(program).check();
}
@Test void testPullConstantThroughConstLast() throws
Exception {
basePullConstantTroughAggregate();
}
@Test void testPullConstantThroughAggregateSimpleNonNullable() throws
Exception {
basePullConstantTroughAggregate();
}
@Test void testPullConstantThroughAggregatePermuted() throws
Exception {
basePullConstantTroughAggregate();
}
@Test void testPullConstantThroughAggregatePermutedConstFirst() throws
Exception {
basePullConstantTroughAggregate();
}
@Test void testPullConstantThroughAggregatePermutedConstGroupBy()
throws Exception {
basePullConstantTroughAggregate();
}
@Test void testPullConstantThroughAggregateConstGroupBy()
throws Exception {
basePullConstantTroughAggregate();
}
@Test void testPullConstantThroughAggregateAllConst()
throws Exception {
basePullConstantTroughAggregate();
}
@Test void testPullConstantThroughAggregateAllLiterals()
throws Exception {
basePullConstantTroughAggregate();
}
@Test void testPullConstantThroughUnion()
throws Exception {
HepProgram program = HepProgram.builder()
.addRuleInstance(UnionPullUpConstantsRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final String sql = "select 2, deptno, job from emp as e1\n"
+ "union all\n"
+ "select 2, deptno, job from emp as e2";
sql(sql)
.withTrim(true)
.with(program)
.check();
}
@Test void testPullConstantThroughUnion2()
throws Exception {
// Negative test: constants should not be pulled up
HepProgram program = HepProgram.builder()
.addRuleInstance(UnionPullUpConstantsRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final String sql = "select 2, deptno, job from emp as e1\n"
+ "union all\n"
+ "select 1, deptno, job from emp as e2";
sql(sql).with(program).checkUnchanged();
}
@Test void testPullConstantThroughUnion3()
throws Exception {
// We should leave at least a single column in each Union input
HepProgram program = HepProgram.builder()
.addRuleInstance(UnionPullUpConstantsRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final String sql = "select 2, 3 from emp as e1\n"
+ "union all\n"
+ "select 2, 3 from emp as e2";
sql(sql)
.withTrim(true)
.with(program)
.check();
}
@Test void testAggregateProjectMerge() {
final String sql = "select x, sum(z), y from (\n"
+ " select deptno as x, empno as y, sal as z, sal * 2 as zz\n"
+ " from emp)\n"
+ "group by x, y";
sql(sql).withRule(AggregateProjectMergeRule.INSTANCE).check();
}
@Test void testAggregateGroupingSetsProjectMerge() {
final String sql = "select x, sum(z), y from (\n"
+ " select deptno as x, empno as y, sal as z, sal * 2 as zz\n"
+ " from emp)\n"
+ "group by rollup(x, y)";
sql(sql).withRule(AggregateProjectMergeRule.INSTANCE).check();
}
@Test void testAggregateExtractProjectRule() {
final String sql = "select sum(sal)\n"
+ "from emp";
HepProgram pre = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final AggregateExtractProjectRule rule =
new AggregateExtractProjectRule(Aggregate.class, LogicalTableScan.class,
RelFactories.LOGICAL_BUILDER);
sql(sql).withPre(pre).withRule(rule).check();
}
@Test void testAggregateExtractProjectRuleWithGroupingSets() {
final String sql = "select empno, deptno, sum(sal)\n"
+ "from emp\n"
+ "group by grouping sets ((empno, deptno),(deptno),(empno))";
HepProgram pre = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final AggregateExtractProjectRule rule =
new AggregateExtractProjectRule(Aggregate.class, LogicalTableScan.class,
RelFactories.LOGICAL_BUILDER);
sql(sql).withPre(pre).withRule(rule).check();
}
/** Test with column used in both grouping set and argument to aggregate
* function. */
@Test void testAggregateExtractProjectRuleWithGroupingSets2() {
final String sql = "select empno, deptno, sum(empno)\n"
+ "from emp\n"
+ "group by grouping sets ((empno, deptno),(deptno),(empno))";
HepProgram pre = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final AggregateExtractProjectRule rule =
new AggregateExtractProjectRule(Aggregate.class, LogicalTableScan.class,
RelFactories.LOGICAL_BUILDER);
sql(sql).withPre(pre).withRule(rule).check();
}
@Test void testAggregateExtractProjectRuleWithFilter() {
final String sql = "select sum(sal) filter (where empno = 40)\n"
+ "from emp";
HepProgram pre = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
// AggregateProjectMergeRule does not merges Project with Filter.
// Force match Aggregate on top of Project once explicitly in unit test.
final AggregateExtractProjectRule rule =
new AggregateExtractProjectRule(
operand(Aggregate.class,
operandJ(Project.class, null,
new Predicate<Project>() {
int matchCount = 0;
public boolean test(Project project) {
return matchCount++ == 0;
}
},
none())),
RelFactories.LOGICAL_BUILDER);
sql(sql).withPre(pre).withRule(rule).checkUnchanged();
}
@Test void testAggregateCaseToFilter() {
final String sql = "select\n"
+ " sum(sal) as sum_sal,\n"
+ " count(distinct case\n"
+ " when job = 'CLERK'\n"
+ " then deptno else null end) as count_distinct_clerk,\n"
+ " sum(case when deptno = 10 then sal end) as sum_sal_d10,\n"
+ " sum(case when deptno = 20 then sal else 0 end) as sum_sal_d20,\n"
+ " sum(case when deptno = 30 then 1 else 0 end) as count_d30,\n"
+ " count(case when deptno = 40 then 'x' end) as count_d40,\n"
+ " count(case when deptno = 20 then 1 end) as count_d20\n"
+ "from emp";
sql(sql).withRule(AggregateCaseToFilterRule.INSTANCE).check();
}
@Test void testPullAggregateThroughUnion() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateUnionAggregateRule.INSTANCE)
.build();
final String sql = "select deptno, job from"
+ " (select deptno, job from emp as e1"
+ " group by deptno,job"
+ " union all"
+ " select deptno, job from emp as e2"
+ " group by deptno,job)"
+ " group by deptno,job";
sql(sql).with(program).check();
}
@Test void testPullAggregateThroughUnion2() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateUnionAggregateRule.AGG_ON_SECOND_INPUT)
.addRuleInstance(AggregateUnionAggregateRule.AGG_ON_FIRST_INPUT)
.build();
final String sql = "select deptno, job from"
+ " (select deptno, job from emp as e1"
+ " group by deptno,job"
+ " union all"
+ " select deptno, job from emp as e2"
+ " group by deptno,job)"
+ " group by deptno,job";
sql(sql).with(program).check();
}
/**
* Once the bottom aggregate pulled through union, we need to add a Project
* if the new input contains a different type from the union.
*/
@Test void testPullAggregateThroughUnionAndAddProjects() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateUnionAggregateRule.INSTANCE)
.build();
final String sql = "select job, deptno from"
+ " (select job, deptno from emp as e1"
+ " group by job, deptno"
+ " union all"
+ " select job, deptno from emp as e2"
+ " group by job, deptno)"
+ " group by job, deptno";
sql(sql).with(program).check();
}
/**
* Make sure the union alias is preserved when the bottom aggregate is
* pulled up through union.
*/
@Test void testPullAggregateThroughUnionWithAlias() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateUnionAggregateRule.INSTANCE)
.build();
final String sql = "select job, c from"
+ " (select job, deptno c from emp as e1"
+ " group by job, deptno"
+ " union all"
+ " select job, deptno from emp as e2"
+ " group by job, deptno)"
+ " group by job, c";
sql(sql).with(program).check();
}
/**
* Create a {@link HepProgram} with common transitive rules.
*/
private HepProgram getTransitiveProgram() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(FilterJoinRule.DUMB_FILTER_ON_JOIN)
.addRuleInstance(FilterJoinRule.JOIN)
.addRuleInstance(FilterProjectTransposeRule.INSTANCE)
.addRuleInstance(FilterSetOpTransposeRule.INSTANCE)
.build();
return program;
}
@Test void testTransitiveInferenceJoin() throws Exception {
final String sql = "select 1 from sales.emp d\n"
+ "inner join sales.emp e on d.deptno = e.deptno where e.deptno > 7";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE).check();
}
@Test void testTransitiveInferenceProject() throws Exception {
final String sql = "select 1 from (select * from sales.emp where deptno > 7) d\n"
+ "inner join sales.emp e on d.deptno = e.deptno";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE).check();
}
@Test void testTransitiveInferenceAggregate() throws Exception {
final String sql = "select 1 from (select deptno, count(*) from sales.emp where deptno > 7\n"
+ "group by deptno) d inner join sales.emp e on d.deptno = e.deptno";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE).check();
}
@Test void testTransitiveInferenceUnion() throws Exception {
final String sql = "select 1 from\n"
+ "(select deptno from sales.emp where deptno > 7\n"
+ "union all select deptno from sales.emp where deptno > 10) d\n"
+ "inner join sales.emp e on d.deptno = e.deptno";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE).check();
}
@Test void testTransitiveInferenceJoin3way() throws Exception {
final String sql = "select 1 from sales.emp d\n"
+ "inner join sales.emp e on d.deptno = e.deptno\n"
+ "inner join sales.emp f on e.deptno = f.deptno\n"
+ "where d.deptno > 7";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE).check();
}
@Test void testTransitiveInferenceJoin3wayAgg() throws Exception {
final String sql = "select 1 from\n"
+ "(select deptno, count(*) from sales.emp where deptno > 7 group by deptno) d\n"
+ "inner join sales.emp e on d.deptno = e.deptno\n"
+ "inner join sales.emp f on e.deptno = f.deptno";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE).check();
}
@Test void testTransitiveInferenceLeftOuterJoin() throws Exception {
final String sql = "select 1 from sales.emp d\n"
+ "left outer join sales.emp e on d.deptno = e.deptno\n"
+ "where d.deptno > 7 and e.deptno > 9";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE).check();
}
@Test void testTransitiveInferenceRightOuterJoin() throws Exception {
final String sql = "select 1 from sales.emp d\n"
+ "right outer join sales.emp e on d.deptno = e.deptno\n"
+ "where d.deptno > 7 and e.deptno > 9";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE).check();
}
@Test void testTransitiveInferenceFullOuterJoin() throws Exception {
final String sql = "select 1 from sales.emp d full outer join sales.emp e\n"
+ "on d.deptno = e.deptno where d.deptno > 7 and e.deptno > 9";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE).checkUnchanged();
}
@Test void testTransitiveInferencePreventProjectPullUp()
throws Exception {
final String sql = "select 1 from (select comm as deptno from sales.emp where deptno > 7) d\n"
+ "inner join sales.emp e on d.deptno = e.deptno";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE).checkUnchanged();
}
@Test void testTransitiveInferencePullUpThruAlias() throws Exception {
final String sql = "select 1 from (select comm as deptno from sales.emp where comm > 7) d\n"
+ "inner join sales.emp e on d.deptno = e.deptno";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE).check();
}
@Test void testTransitiveInferenceConjunctInPullUp() throws Exception {
final String sql = "select 1 from sales.emp d\n"
+ "inner join sales.emp e on d.deptno = e.deptno\n"
+ "where d.deptno in (7, 9) or d.deptno > 10";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE).check();
}
@Test void testTransitiveInferenceNoPullUpExprs() throws Exception {
final String sql = "select 1 from sales.emp d\n"
+ "inner join sales.emp e on d.deptno = e.deptno\n"
+ "where d.deptno in (7, 9) or d.comm > 10";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE).checkUnchanged();
}
@Test void testTransitiveInferenceUnion3way() throws Exception {
final String sql = "select 1 from\n"
+ "(select deptno from sales.emp where deptno > 7\n"
+ "union all\n"
+ "select deptno from sales.emp where deptno > 10\n"
+ "union all\n"
+ "select deptno from sales.emp where deptno > 1) d\n"
+ "inner join sales.emp e on d.deptno = e.deptno";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE).check();
}
@Test void testTransitiveInferenceUnion3wayOr() throws Exception {
final String sql = "select 1 from\n"
+ "(select empno, deptno from sales.emp where deptno > 7 or empno < 10\n"
+ "union all\n"
+ "select empno, deptno from sales.emp where deptno > 10 or empno < deptno\n"
+ "union all\n"
+ "select empno, deptno from sales.emp where deptno > 1) d\n"
+ "inner join sales.emp e on d.deptno = e.deptno";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE).checkUnchanged();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-443">[CALCITE-443]
* getPredicates from a union is not correct</a>. */
@Test void testTransitiveInferenceUnionAlwaysTrue() throws Exception {
final String sql = "select d.deptno, e.deptno from\n"
+ "(select deptno from sales.emp where deptno < 4) d\n"
+ "inner join\n"
+ "(select deptno from sales.emp where deptno > 7\n"
+ "union all select deptno from sales.emp) e\n"
+ "on d.deptno = e.deptno";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE).check();
}
@Test void testTransitiveInferenceConstantEquiPredicate()
throws Exception {
final String sql = "select 1 from sales.emp d\n"
+ "inner join sales.emp e on d.deptno = e.deptno where 1 = 1";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE).checkUnchanged();
}
@Test void testTransitiveInferenceComplexPredicate() throws Exception {
final String sql = "select 1 from sales.emp d\n"
+ "inner join sales.emp e on d.deptno = e.deptno\n"
+ "where d.deptno > 7 and e.sal = e.deptno and d.comm = d.deptno\n"
+ "and d.comm + d.deptno > d.comm/2";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE).check();
}
@Test void testPullConstantIntoProject() throws Exception {
final String sql = "select deptno, deptno + 1, empno + deptno\n"
+ "from sales.emp where deptno = 10";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE,
ReduceExpressionsRule.PROJECT_INSTANCE)
.check();
}
@Test void testPullConstantIntoFilter() throws Exception {
final String sql = "select * from (select * from sales.emp where deptno = 10)\n"
+ "where deptno + 5 > empno";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE,
ReduceExpressionsRule.FILTER_INSTANCE)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1995">[CALCITE-1995]
* Remove predicates from Filter if they can be proved to be always true or
* false</a>. */
@Test void testSimplifyFilter() throws Exception {
final String sql = "select * from (select * from sales.emp where deptno > 10)\n"
+ "where empno > 3 and deptno > 5";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE,
ReduceExpressionsRule.FILTER_INSTANCE)
.check();
}
@Test void testPullConstantIntoJoin() throws Exception {
final String sql = "select * from (select * from sales.emp where empno = 10) as e\n"
+ "left join sales.dept as d on e.empno = d.deptno";
sql(sql).withPre(getTransitiveProgram())
.withRule(JoinPushTransitivePredicatesRule.INSTANCE,
ReduceExpressionsRule.JOIN_INSTANCE)
.check();
}
@Test void testPullConstantIntoJoin2() throws Exception {
final String sql = "select * from (select * from sales.emp where empno = 10) as e\n"
+ "join sales.dept as d on e.empno = d.deptno and e.deptno + e.empno = d.deptno + 5";
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(JoinPushTransitivePredicatesRule.INSTANCE)
.addRuleCollection(
ImmutableList.of(
ReduceExpressionsRule.PROJECT_INSTANCE,
FilterProjectTransposeRule.INSTANCE,
ReduceExpressionsRule.JOIN_INSTANCE))
.build();
sql(sql).withPre(getTransitiveProgram()).with(program).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2110">[CALCITE-2110]
* ArrayIndexOutOfBoundsException in RexSimplify when using
* ReduceExpressionsRule.JOIN_INSTANCE</a>. */
@Test void testCorrelationScalarAggAndFilter() {
final String sql = "SELECT e1.empno\n"
+ "FROM emp e1, dept d1 where e1.deptno = d1.deptno\n"
+ "and e1.deptno < 10 and d1.deptno < 15\n"
+ "and e1.sal > (select avg(sal) from emp e2 where e1.empno = e2.empno)";
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.PROJECT_INSTANCE)
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(ReduceExpressionsRule.JOIN_INSTANCE)
.build();
sql(sql)
.withDecorrelation(true)
.withTrim(true)
.expand(true)
.withPre(program)
.with(program)
.checkUnchanged();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3111">[CALCITE-3111]
* Allow custom implementations of Correlate in RelDecorrelator </a>
*/
@Test void testCustomDecorrelate() {
final String sql = "SELECT e1.empno\n"
+ "FROM emp e1, dept d1 where e1.deptno = d1.deptno\n"
+ "and e1.deptno < 10 and d1.deptno < 15\n"
+ "and e1.sal > (select avg(sal) from emp e2 where e1.empno = e2.empno)";
// Convert sql to rel
RelRoot root = tester.convertSqlToRel(sql);
// Create a duplicate rel tree with a custom correlate instead of logical correlate
LogicalCorrelate logicalCorrelate = (LogicalCorrelate) root.rel.getInput(0).getInput(0);
CustomCorrelate customCorrelate = new CustomCorrelate(
logicalCorrelate.getCluster(),
logicalCorrelate.getTraitSet(),
logicalCorrelate.getLeft(),
logicalCorrelate.getRight(),
logicalCorrelate.getCorrelationId(),
logicalCorrelate.getRequiredColumns(),
logicalCorrelate.getJoinType());
RelNode newRoot = root.rel.copy(
root.rel.getTraitSet(),
ImmutableList.of(
root.rel.getInput(0).copy(
root.rel.getInput(0).getTraitSet(),
ImmutableList.<RelNode>of(customCorrelate))));
// Decorrelate both trees using the same relBuilder
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
RelNode logicalDecorrelated = RelDecorrelator.decorrelateQuery(root.rel, relBuilder);
RelNode customDecorrelated = RelDecorrelator.decorrelateQuery(newRoot, relBuilder);
String logicalDecorrelatedPlan = NL + RelOptUtil.toString(logicalDecorrelated);
String customDecorrelatedPlan = NL + RelOptUtil.toString(customDecorrelated);
// Ensure that the plans are equal
getDiffRepos().assertEquals("Comparing Plans from LogicalCorrelate and CustomCorrelate",
logicalDecorrelatedPlan, customDecorrelatedPlan);
}
@Test void testProjectWindowTransposeRule() {
final String sql = "select count(empno) over(), deptno from emp";
sql(sql).withRule(ProjectToWindowRule.PROJECT,
ProjectWindowTransposeRule.INSTANCE)
.check();
}
@Test void testProjectWindowTransposeRuleWithConstants() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ProjectToWindowRule.PROJECT)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.addRuleInstance(ProjectWindowTransposeRule.INSTANCE)
.build();
final String sql = "select col1, col2\n"
+ "from (\n"
+ " select empno,\n"
+ " sum(100) over (partition by deptno order by sal) as col1,\n"
+ " sum(1000) over(partition by deptno order by sal) as col2\n"
+ " from emp)";
sql(sql).with(program).check();
}
/** While it's probably valid relational algebra for a Project to contain
* a RexOver inside a RexOver, ProjectMergeRule should not bring it about. */
@Test void testProjectMergeShouldIgnoreOver() {
final String sql = "select row_number() over (order by deptno), col1\n"
+ "from (\n"
+ " select deptno,\n"
+ " sum(100) over (partition by deptno order by sal) as col1\n"
+ " from emp)";
sql(sql).withRule(ProjectMergeRule.INSTANCE).checkUnchanged();
}
@Test void testAggregateProjectPullUpConstants() {
final String sql = "select job, empno, sal, sum(sal) as s\n"
+ "from emp where empno = 10\n"
+ "group by job, empno, sal";
sql(sql).withRule(AggregateProjectPullUpConstantsRule.INSTANCE2).check();
}
@Test void testAggregateProjectPullUpConstants2() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select ename, sal\n"
+ "from (select '1', ename, sal from emp where ename = 'John') subq\n"
+ "group by ename, sal";
sql(sql).withPre(preProgram)
.withRule(AggregateProjectPullUpConstantsRule.INSTANCE2)
.check();
}
@Test void testPushFilterWithRank() throws Exception {
final String sql = "select e1.ename, r\n"
+ "from (\n"
+ " select ename, "
+ " rank() over(partition by deptno order by sal) as r "
+ " from emp) e1\n"
+ "where r < 2";
sql(sql).withRule(FilterProjectTransposeRule.INSTANCE)
.checkUnchanged();
}
@Test void testPushFilterWithRankExpr() throws Exception {
final String sql = "select e1.ename, r\n"
+ "from (\n"
+ " select ename,\n"
+ " rank() over(partition by deptno order by sal) + 1 as r "
+ " from emp) e1\n"
+ "where r < 2";
sql(sql).withRule(FilterProjectTransposeRule.INSTANCE)
.checkUnchanged();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-841">[CALCITE-841]
* Redundant windows when window function arguments are expressions</a>. */
@Test void testExpressionInWindowFunction() {
HepProgramBuilder builder = new HepProgramBuilder();
builder.addRuleClass(ProjectToWindowRule.class);
HepPlanner hepPlanner = new HepPlanner(builder.build());
hepPlanner.addRule(ProjectToWindowRule.PROJECT);
final String sql = "select\n"
+ " sum(deptno) over(partition by deptno order by sal) as sum1,\n"
+ "sum(deptno + sal) over(partition by deptno order by sal) as sum2\n"
+ "from emp";
sql(sql)
.with(hepPlanner)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-888">[CALCITE-888]
* Overlay window loses PARTITION BY list</a>. */
@Test void testWindowInParenthesis() {
HepProgramBuilder builder = new HepProgramBuilder();
builder.addRuleClass(ProjectToWindowRule.class);
HepPlanner hepPlanner = new HepPlanner(builder.build());
hepPlanner.addRule(ProjectToWindowRule.PROJECT);
final String sql = "select count(*) over (w), count(*) over w\n"
+ "from emp\n"
+ "window w as (partition by empno order by empno)";
sql(sql)
.with(hepPlanner)
.check();
}
/** Test case for DX-11490
* Make sure the planner doesn't fail over wrong push down
* of is null */
@Test void testIsNullPushDown() {
HepProgramBuilder preBuilder = new HepProgramBuilder();
preBuilder.addRuleInstance(ProjectToWindowRule.PROJECT);
HepProgramBuilder builder = new HepProgramBuilder();
builder.addRuleInstance(ReduceExpressionsRule.PROJECT_INSTANCE);
builder.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE);
HepPlanner hepPlanner = new HepPlanner(builder.build());
final String sql = "select empno, deptno, w_count from (\n"
+ " select empno, deptno, count(empno) over (w) w_count\n"
+ " from emp\n"
+ " window w as (partition by deptno order by empno)\n"
+ ") sub_query where w_count is null";
sql(sql)
.withPre(preBuilder.build())
.with(hepPlanner)
.check();
}
@Test void testIsNullPushDown2() {
HepProgramBuilder preBuilder = new HepProgramBuilder();
preBuilder.addRuleInstance(ProjectToWindowRule.PROJECT);
HepProgramBuilder builder = new HepProgramBuilder();
builder.addRuleInstance(ReduceExpressionsRule.PROJECT_INSTANCE);
builder.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE);
HepPlanner hepPlanner = new HepPlanner(builder.build());
final String sql = "select empno, deptno, w_count from (\n"
+ " select empno, deptno, count(empno) over (ROWS BETWEEN 10 PRECEDING AND 1 PRECEDING) w_count\n"
+ " from emp\n"
+ ") sub_query where w_count is null";
sql(sql)
.withPre(preBuilder.build())
.with(hepPlanner)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-750">[CALCITE-750]
* Allow windowed aggregate on top of regular aggregate</a>. */
@Test void testNestedAggregates() {
final String sql = "SELECT\n"
+ " avg(sum(sal) + 2 * min(empno) + 3 * avg(empno))\n"
+ " over (partition by deptno)\n"
+ "from emp\n"
+ "group by deptno";
sql(sql).withRule(ProjectToWindowRule.PROJECT).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2078">[CALCITE-2078]
* Aggregate functions in OVER clause</a>. */
@Test void testWindowFunctionOnAggregations() {
final String sql = "SELECT\n"
+ " min(empno),\n"
+ " sum(sal),\n"
+ " sum(sum(sal))\n"
+ " over (partition by min(empno) order by sum(sal))\n"
+ "from emp\n"
+ "group by deptno";
sql(sql).withRule(ProjectToWindowRule.PROJECT).check();
}
@Test void testPushAggregateThroughJoin1() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select e.job,d.name\n"
+ "from (select * from sales.emp where ename = 'A') as e\n"
+ "join sales.dept as d on e.job = d.name\n"
+ "group by e.job,d.name";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Test case for
* outer join, group by on non-join keys, group by on non-null generating side only */
@Test void testPushAggregateThroughOuterJoin1() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select e.ename\n"
+ "from (select * from sales.emp where ename = 'A') as e\n"
+ "left outer join sales.dept as d on e.job = d.name\n"
+ "group by e.ename";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Test case for
* outer join, group by on non-join keys, on null generating side only */
@Test void testPushAggregateThroughOuterJoin2() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select d.ename\n"
+ "from (select * from sales.emp where ename = 'A') as e\n"
+ "left outer join sales.emp as d on e.job = d.job\n"
+ "group by d.ename";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Test case for
* outer join, group by on both side on non-join keys */
@Test void testPushAggregateThroughOuterJoin3() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select e.ename, d.mgr\n"
+ "from (select * from sales.emp where ename = 'A') as e\n"
+ "left outer join sales.emp as d on e.job = d.job\n"
+ "group by e.ename,d.mgr";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Test case for
* outer join, group by on key same as join key, group by on non-null generating side */
@Test void testPushAggregateThroughOuterJoin4() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select e.job\n"
+ "from (select * from sales.emp where ename = 'A') as e\n"
+ "left outer join sales.dept as d on e.job = d.name\n"
+ "group by e.job";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Test case for
* outer join, group by on key same as join key, group by on null generating side */
@Test void testPushAggregateThroughOuterJoin5() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select d.name\n"
+ "from (select * from sales.emp where ename = 'A') as e\n"
+ "left outer join sales.dept as d on e.job = d.name\n"
+ "group by d.name";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Test case for
* outer join, group by on key same as join key, group by on both side */
@Test void testPushAggregateThroughOuterJoin6() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select e.job,d.name\n"
+ "from (select * from sales.emp where ename = 'A') as e\n"
+ "left outer join sales.dept as d on e.job = d.name\n"
+ "group by e.job,d.name";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Test case for
* outer join, group by key is susbset of join keys, group by on non-null generating side */
@Test void testPushAggregateThroughOuterJoin7() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select e.job\n"
+ "from (select * from sales.emp where ename = 'A') as e\n"
+ "left outer join sales.dept as d on e.job = d.name\n"
+ "and e.deptno + e.empno = d.deptno + 5\n"
+ "group by e.job";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Test case for
* outer join, group by key is susbset of join keys, group by on null generating side */
@Test void testPushAggregateThroughOuterJoin8() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select d.name\n"
+ "from (select * from sales.emp where ename = 'A') as e\n"
+ "left outer join sales.dept as d on e.job = d.name\n"
+ "and e.deptno + e.empno = d.deptno + 5\n"
+ "group by d.name";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Test case for
* outer join, group by key is susbset of join keys, group by on both sides */
@Test void testPushAggregateThroughOuterJoin9() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select e.job, d.name\n"
+ "from (select * from sales.emp where ename = 'A') as e\n"
+ "left outer join sales.dept as d on e.job = d.name\n"
+ "and e.deptno + e.empno = d.deptno + 5\n"
+ "group by e.job, d.name";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Test case for
* outer join, with aggregate functions */
@Test void testPushAggregateThroughOuterJoin10() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select count(e.ename)\n"
+ "from (select * from sales.emp where empno = 10) as e\n"
+ "left outer join sales.emp as d on e.job = d.job\n"
+ "group by e.ename,d.mgr";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.checkUnchanged();
}
/** Test case for
* non-equi outer join */
@Test void testPushAggregateThroughOuterJoin11() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select e.empno,d.deptno\n"
+ "from (select * from sales.emp where empno = 10) as e\n"
+ "left outer join sales.dept as d on e.empno < d.deptno\n"
+ "group by e.empno,d.deptno";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.checkUnchanged();
}
/** Test case for
* right outer join, group by on key same as join key, group by on (left)null generating side */
@Test void testPushAggregateThroughOuterJoin12() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select e.job\n"
+ "from (select * from sales.emp where ename = 'A') as e\n"
+ "right outer join sales.dept as d on e.job = d.name\n"
+ "group by e.job";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Test case for
* full outer join, group by on key same as join key, group by on one side */
@Test void testPushAggregateThroughOuterJoin13() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select e.job\n"
+ "from (select * from sales.emp where ename = 'A') as e\n"
+ "full outer join sales.dept as d on e.job = d.name\n"
+ "group by e.job";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Test case for
* full outer join, group by on key same as join key, group by on both side */
@Test void testPushAggregateThroughOuterJoin14() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select e.mgr, d.mgr\n"
+ "from sales.emp as e\n"
+ "full outer join sales.emp as d on e.mgr = d.mgr\n"
+ "group by d.mgr, e.mgr";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Test case for
* full outer join, group by on both side on non-join keys */
@Test void testPushAggregateThroughOuterJoin15() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select e.ename, d.mgr\n"
+ "from (select * from sales.emp where ename = 'A') as e\n"
+ "full outer join sales.emp as d on e.job = d.job\n"
+ "group by e.ename,d.mgr";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Test case for
* full outer join, group by key is susbset of join keys */
@Test void testPushAggregateThroughOuterJoin16() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select e.job\n"
+ "from (select * from sales.emp where ename = 'A') as e\n"
+ "full outer join sales.dept as d on e.job = d.name\n"
+ "and e.deptno + e.empno = d.deptno + 5\n"
+ "group by e.job";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
@Test void testPushAggregateThroughJoin2() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select e.job,d.name\n"
+ "from (select * from sales.emp where ename = 'A') as e\n"
+ "join sales.dept as d on e.job = d.name\n"
+ "and e.deptno + e.empno = d.deptno + 5\n"
+ "group by e.job,d.name";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
@Test void testPushAggregateThroughJoin3() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select e.empno,d.deptno\n"
+ "from (select * from sales.emp where empno = 10) as e\n"
+ "join sales.dept as d on e.empno < d.deptno\n"
+ "group by e.empno,d.deptno";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.checkUnchanged();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1544">[CALCITE-1544]
* AggregateJoinTransposeRule fails to preserve row type</a>. */
@Test void testPushAggregateThroughJoin4() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select e.deptno\n"
+ "from sales.emp as e join sales.dept as d on e.deptno = d.deptno\n"
+ "group by e.deptno";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
@Test void testPushAggregateThroughJoin5() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select e.deptno, d.deptno\n"
+ "from sales.emp as e join sales.dept as d on e.deptno = d.deptno\n"
+ "group by e.deptno, d.deptno";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2200">[CALCITE-2200]
* Infinite loop for JoinPushTransitivePredicatesRule</a>. */
@Test void testJoinPushTransitivePredicatesRule() {
HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(FilterJoinRule.FILTER_ON_JOIN)
.addRuleInstance(FilterJoinRule.JOIN)
.addRuleInstance(JoinPushTransitivePredicatesRule.INSTANCE)
.build();
final HepProgram emptyProgram = new HepProgramBuilder().build();
final String sql = "select d.deptno from sales.emp d where d.deptno\n"
+ "IN (select e.deptno from sales.emp e "
+ "where e.deptno = d.deptno or e.deptno = 4)";
sql(sql).withPre(preProgram).with(emptyProgram).checkUnchanged();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2205">[CALCITE-2205]
* One more infinite loop for JoinPushTransitivePredicatesRule</a>. */
@Test void testJoinPushTransitivePredicatesRule2() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(FilterJoinRule.FILTER_ON_JOIN)
.addRuleInstance(FilterJoinRule.JOIN)
.addRuleInstance(JoinPushTransitivePredicatesRule.INSTANCE)
.build();
final String sql = "select n1.SAL\n"
+ "from EMPNULLABLES_20 n1\n"
+ "where n1.SAL IN (\n"
+ " select n2.SAL\n"
+ " from EMPNULLABLES_20 n2\n"
+ " where n1.SAL = n2.SAL or n1.SAL = 4)";
sql(sql).withDecorrelation(true).with(program).checkUnchanged();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2275">[CALCITE-2275]
* JoinPushTransitivePredicatesRule wrongly pushes down NOT condition</a>. */
@Test void testInferringPredicatesWithNotOperatorInJoinCondition() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(FilterJoinRule.FILTER_ON_JOIN)
.addRuleInstance(FilterJoinRule.JOIN)
.addRuleInstance(JoinPushTransitivePredicatesRule.INSTANCE)
.build();
final String sql = "select * from sales.emp d\n"
+ "join sales.emp e on e.deptno = d.deptno and d.deptno not in (4, 6)";
sql(sql).withDecorrelation(true).with(program).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2195">[CALCITE-2195]
* AggregateJoinTransposeRule fails to aggregate over unique column</a>. */
@Test void testPushAggregateThroughJoin6() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateJoinTransposeRule.EXTENDED)
.build();
final String sql = "select sum(B.sal)\n"
+ "from sales.emp as A\n"
+ "join (select distinct sal from sales.emp) as B\n"
+ "on A.sal=B.sal\n";
sql(sql).withPre(preProgram).with(program).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2278">[CALCITE-2278]
* AggregateJoinTransposeRule fails to split aggregate call if input contains
* an aggregate call and has distinct rows</a>. */
@Test void testPushAggregateThroughJoinWithUniqueInput() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select A.job, B.mgr, A.deptno,\n"
+ "max(B.hiredate1) as hiredate1, sum(B.comm1) as comm1\n"
+ "from sales.emp as A\n"
+ "join (select mgr, sal, max(hiredate) as hiredate1,\n"
+ " sum(comm) as comm1 from sales.emp group by mgr, sal) as B\n"
+ "on A.sal=B.sal\n"
+ "group by A.job, B.mgr, A.deptno";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** SUM is the easiest aggregate function to split. */
@Test void testPushAggregateSumThroughJoin() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select e.job,sum(sal)\n"
+ "from (select * from sales.emp where ename = 'A') as e\n"
+ "join sales.dept as d on e.job = d.name\n"
+ "group by e.job,d.name";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2105">[CALCITE-2105]
* AggregateJoinTransposeRule incorrectly makes a SUM NOT NULL when Aggregate
* has no group keys</a>. */
@Test void testPushAggregateSumWithoutGroupKeyThroughJoin() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select sum(sal)\n"
+ "from (select * from sales.emp where ename = 'A') as e\n"
+ "join sales.dept as d on e.job = d.name";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2108">[CALCITE-2108]
* AggregateJoinTransposeRule incorrectly splits a SUM0 call when Aggregate
* has no group keys</a>.
*
* <p>Similar to {@link #testPushAggregateSumThroughJoin()},
* but also uses {@link AggregateReduceFunctionsRule}. */
@Test void testPushAggregateSumThroughJoinAfterAggregateReduce() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateReduceFunctionsRule.INSTANCE)
.addRuleInstance(AggregateJoinTransposeRule.EXTENDED)
.build();
final String sql = "select sum(sal)\n"
+ "from (select * from sales.emp where ename = 'A') as e\n"
+ "join sales.dept as d on e.job = d.name";
sql(sql).withPre(preProgram)
.with(program)
.check();
}
/** Push a variety of aggregate functions. */
@Test void testPushAggregateFunctionsThroughJoin() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select e.job,\n"
+ " min(sal) as min_sal, min(e.deptno) as min_deptno,\n"
+ " sum(sal) + 1 as sum_sal_plus, max(sal) as max_sal,\n"
+ " sum(sal) as sum_sal_2, count(sal) as count_sal,\n"
+ " count(mgr) as count_mgr\n"
+ "from sales.emp as e\n"
+ "join sales.dept as d on e.job = d.name\n"
+ "group by e.job,d.name";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Push a aggregate functions into a relation that is unique on the join
* key. */
@Test void testPushAggregateThroughJoinDistinct() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql = "select d.name,\n"
+ " sum(sal) as sum_sal, count(*) as c\n"
+ "from sales.emp as e\n"
+ "join (select distinct name from sales.dept) as d\n"
+ " on e.job = d.name\n"
+ "group by d.name";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Push count(*) through join, no GROUP BY. */
@Test void testPushAggregateSumNoGroup() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.build();
final String sql =
"select count(*) from sales.emp join sales.dept on job = name";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3076">[CALCITE-3076]
* AggregateJoinTransposeRule throws error for unique under aggregate keys when
* generating merged calls</a>.*/
@Test void testPushAggregateThroughJoinOnEmptyLogicalValues() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(ReduceExpressionsRule.FilterReduceExpressionsRule.FILTER_INSTANCE)
.build();
final String sql =
"select count(*) volume, sum(C1.sal) C1_sum_sal "
+ "from (select sal, ename from sales.emp where 1=2) C1 "
+ "inner join (select ename from sales.emp) C2 "
+ "on C1.ename = C2.ename ";
sql(sql).withPre(preProgram)
.withRule(AggregateJoinTransposeRule.EXTENDED)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2249">[CALCITE-2249]
* AggregateJoinTransposeRule generates inequivalent nodes if Aggregate relNode contains
* distinct aggregate function.</a>. */
@Test void testPushDistinctAggregateIntoJoin() throws Exception {
final String sql =
"select count(distinct sal) from sales.emp join sales.dept on job = name";
sql(sql).withRule(AggregateJoinTransposeRule.EXTENDED)
.checkUnchanged();
}
/**
* Test case for AggregateMergeRule, should merge 2 aggregates
* into a single aggregate.
*/
@Test void testAggregateMerge1() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateMergeRule.INSTANCE)
.build();
final String sql = "select deptno c, min(y), max(z) z,\n"
+ "sum(r), sum(m) n, sum(x) sal from (\n"
+ " select deptno, ename, sum(sal) x, max(sal) z,\n"
+ " min(sal) y, count(hiredate) m, count(mgr) r\n"
+ " from sales.emp group by deptno, ename) t\n"
+ "group by deptno";
sql(sql).withPre(preProgram)
.with(program)
.check();
}
/**
* Test case for AggregateMergeRule, should merge 2 aggregates
* into a single aggregate, top aggregate is not simple aggregate.
*/
@Test void testAggregateMerge2() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateMergeRule.INSTANCE)
.build();
final String sql = "select deptno, empno, sum(x), sum(y)\n"
+ "from (\n"
+ " select ename, empno, deptno, sum(sal) x, count(mgr) y\n"
+ " from sales.emp\n"
+ " group by deptno, ename, empno) t\n"
+ "group by grouping sets(deptno, empno)";
sql(sql).withPre(preProgram)
.with(program)
.check();
}
/**
* Test case for AggregateMergeRule, should not merge 2 aggregates
* into a single aggregate, since lower aggregate is not simple aggregate.
*/
@Test void testAggregateMerge3() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateMergeRule.INSTANCE)
.build();
final String sql = "select deptno, sum(x) from (\n"
+ " select ename, deptno, sum(sal) x from\n"
+ " sales.emp group by cube(deptno, ename)) t\n"
+ "group by deptno";
sql(sql).withPre(preProgram).with(program)
.checkUnchanged();
}
/**
* Test case for AggregateMergeRule, should not merge 2 aggregates
* into a single aggregate, since it contains distinct aggregate
* function.
*/
@Test void testAggregateMerge4() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateMergeRule.INSTANCE)
.build();
final String sql = "select deptno, sum(x) from (\n"
+ " select ename, deptno, count(distinct sal) x\n"
+ " from sales.emp group by deptno, ename) t\n"
+ "group by deptno";
sql(sql).withPre(preProgram).with(program)
.checkUnchanged();
}
/**
* Test case for AggregateMergeRule, should not merge 2 aggregates
* into a single aggregate, since AVG doesn't support splitting.
*/
@Test void testAggregateMerge5() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateMergeRule.INSTANCE)
.build();
final String sql = "select deptno, avg(x) from (\n"
+ " select mgr, deptno, avg(sal) x from\n"
+ " sales.emp group by deptno, mgr) t\n"
+ "group by deptno";
sql(sql).withPre(preProgram).with(program)
.checkUnchanged();
}
/**
* Test case for AggregateMergeRule, should not merge 2 aggregates
* into a single aggregate, since top agg has no group key, and
* lower agg function is COUNT.
*/
@Test void testAggregateMerge6() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateMergeRule.INSTANCE)
.build();
final String sql = "select sum(x) from (\n"
+ "select mgr, deptno, count(sal) x from\n"
+ "sales.emp group by deptno, mgr) t";
sql(sql).withPre(preProgram).with(program)
.checkUnchanged();
}
/**
* Test case for AggregateMergeRule, should not merge 2 aggregates
* into a single aggregate, since top agg contains empty grouping set,
* and lower agg function is COUNT.
*/
@Test void testAggregateMerge7() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateMergeRule.INSTANCE)
.build();
final String sql = "select mgr, deptno, sum(x) from (\n"
+ " select mgr, deptno, count(sal) x from\n"
+ " sales.emp group by deptno, mgr) t\n"
+ "group by cube(mgr, deptno)";
sql(sql).withPre(preProgram).with(program)
.checkUnchanged();
}
/**
* Test case for AggregateMergeRule, should merge 2 aggregates
* into a single aggregate, since both top and bottom aggregates
* contains empty grouping set and they are mergable.
*/
@Test void testAggregateMerge8() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateMergeRule.INSTANCE)
.build();
final String sql = "select sum(x) x, min(y) z from (\n"
+ " select sum(sal) x, min(sal) y from sales.emp)";
sql(sql).withPre(preProgram)
.with(program)
.check();
}
/**
* Test case for AggregateRemoveRule, should remove aggregates since
* empno is unique and all aggregate functions are splittable.
*/
@Test void testAggregateRemove1() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateRemoveRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final String sql = "select empno, sum(sal), min(sal), max(sal), "
+ "bit_and(distinct sal), bit_or(sal), count(distinct sal) "
+ "from sales.emp group by empno, deptno\n";
sql(sql).with(program)
.check();
}
/**
* Test case for AggregateRemoveRule, should remove aggregates since
* empno is unique and there are no aggregate functions.
*/
@Test void testAggregateRemove2() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateRemoveRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final String sql = "select distinct empno, deptno from sales.emp\n";
sql(sql).with(program)
.check();
}
/**
* Test case for AggregateRemoveRule, should remove aggregates since
* empno is unique and all aggregate functions are splittable. Count
* aggregate function should be transformed to CASE function call
* because mgr is nullable.
*/
@Test void testAggregateRemove3() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateRemoveRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final String sql = "select empno, count(mgr) "
+ "from sales.emp group by empno, deptno\n";
sql(sql).with(program)
.check();
}
/**
* Negative test case for AggregateRemoveRule, should not
* remove aggregate because avg is not splittable.
*/
@Test void testAggregateRemove4() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateRemoveRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final String sql = "select empno, max(sal), avg(sal) "
+ "from sales.emp group by empno, deptno\n";
sql(sql).with(program)
.checkUnchanged();
}
/**
* Negative test case for AggregateRemoveRule, should not
* remove non-simple aggregates.
*/
@Test void testAggregateRemove5() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateRemoveRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final String sql = "select empno, deptno, sum(sal) "
+ "from sales.emp group by cube(empno, deptno)\n";
sql(sql).with(program)
.checkUnchanged();
}
/**
* Negative test case for AggregateRemoveRule, should not
* remove aggregate because deptno is not unique.
*/
@Test void testAggregateRemove6() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateRemoveRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final String sql = "select deptno, max(sal) "
+ "from sales.emp group by deptno\n";
sql(sql).with(program)
.checkUnchanged();
}
/**
* The top Aggregate should be removed -- given "deptno=100",
* the input of top Aggregate must be already distinct by "mgr"
*/
@Test void testAggregateRemove7() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateRemoveRule.INSTANCE)
.addRuleInstance(ProjectMergeRule.INSTANCE)
.build();
final String sql = ""
+ "select mgr, sum(sum_sal)\n"
+ "from\n"
+ "(select mgr, deptno, sum(sal) sum_sal\n"
+ " from sales.emp\n"
+ " group by mgr, deptno)\n"
+ "where deptno=100\n"
+ "group by mgr";
sql(sql)
.with(program)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2712">[CALCITE-2712]
* Should remove the left join since the aggregate has no call and
* only uses column in the left input of the bottom join as group key.</a>. */
@Test void testAggregateJoinRemove1() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateJoinRemoveRule.INSTANCE)
.build();
final String sql =
"select distinct e.deptno from sales.emp e\n"
+ "left outer join sales.dept d on e.deptno = d.deptno";
sql(sql).with(program)
.check();
}
/** Similar to {@link #testAggregateJoinRemove1()} but has aggregate
* call with distinct. */
@Test void testAggregateJoinRemove2() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateJoinRemoveRule.INSTANCE)
.build();
final String sql =
"select e.deptno, count(distinct e.job) from sales.emp e\n"
+ "left outer join sales.dept d on e.deptno = d.deptno\n"
+ "group by e.deptno";
sql(sql).with(program)
.check();
}
/** Similar to {@link #testAggregateJoinRemove1()} but should not
* remove the left join since the aggregate uses column in the right
* input of the bottom join. */
@Test void testAggregateJoinRemove3() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateJoinRemoveRule.INSTANCE)
.build();
final String sql =
"select e.deptno, count(distinct d.name) from sales.emp e\n"
+ "left outer join sales.dept d on e.deptno = d.deptno\n"
+ "group by e.deptno";
sql(sql).with(program)
.check();
}
/** Similar to {@link #testAggregateJoinRemove1()} but right join. */
@Test void testAggregateJoinRemove4() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateJoinRemoveRule.INSTANCE)
.build();
final String sql =
"select distinct d.deptno from sales.emp e\n"
+ "right outer join sales.dept d on e.deptno = d.deptno";
sql(sql).with(program)
.check();
}
/** Similar to {@link #testAggregateJoinRemove2()} but right join. */
@Test void testAggregateJoinRemove5() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateJoinRemoveRule.INSTANCE)
.build();
final String sql =
"select d.deptno, count(distinct d.name) from sales.emp e\n"
+ "right outer join sales.dept d on e.deptno = d.deptno\n"
+ "group by d.deptno";
sql(sql).with(program)
.check();
}
/** Similar to {@link #testAggregateJoinRemove3()} but right join. */
@Test void testAggregateJoinRemove6() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateJoinRemoveRule.INSTANCE)
.build();
final String sql =
"select d.deptno, count(distinct e.job) from sales.emp e\n"
+ "right outer join sales.dept d on e.deptno = d.deptno\n"
+ "group by d.deptno";
sql(sql).with(program)
.check();
}
/** Similar to {@link #testAggregateJoinRemove1()};
* Should remove the bottom join since the aggregate has no aggregate
* call. */
@Test void testAggregateJoinRemove7() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateJoinJoinRemoveRule.INSTANCE)
.build();
final String sql = "SELECT distinct e.deptno\n"
+ "FROM sales.emp e\n"
+ "LEFT JOIN sales.dept d1 ON e.deptno = d1.deptno\n"
+ "LEFT JOIN sales.dept d2 ON e.deptno = d2.deptno";
sql(sql).with(program)
.check();
}
/** Similar to {@link #testAggregateJoinRemove7()} but has aggregate
* call. */
@Test void testAggregateJoinRemove8() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateJoinJoinRemoveRule.INSTANCE)
.build();
final String sql = "SELECT e.deptno, COUNT(DISTINCT d2.name)\n"
+ "FROM sales.emp e\n"
+ "LEFT JOIN sales.dept d1 ON e.deptno = d1.deptno\n"
+ "LEFT JOIN sales.dept d2 ON e.deptno = d2.deptno\n"
+ "GROUP BY e.deptno";
sql(sql).with(program)
.check();
}
/** Similar to {@link #testAggregateJoinRemove7()} but use columns in
* the right input of the top join. */
@Test void testAggregateJoinRemove9() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateJoinJoinRemoveRule.INSTANCE)
.build();
final String sql = "SELECT distinct e.deptno, d2.name\n"
+ "FROM sales.emp e\n"
+ "LEFT JOIN sales.dept d1 ON e.deptno = d1.deptno\n"
+ "LEFT JOIN sales.dept d2 ON e.deptno = d2.deptno";
sql(sql).with(program)
.check();
}
/** Similar to {@link #testAggregateJoinRemove1()};
* Should not remove the bottom join since the aggregate uses column in the
* right input of bottom join. */
@Test void testAggregateJoinRemove10() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(AggregateProjectMergeRule.INSTANCE)
.addRuleInstance(AggregateJoinJoinRemoveRule.INSTANCE)
.build();
final String sql = "SELECT e.deptno, COUNT(DISTINCT d1.name, d2.name)\n"
+ "FROM sales.emp e\n"
+ "LEFT JOIN sales.dept d1 ON e.deptno = d1.deptno\n"
+ "LEFT JOIN sales.dept d2 ON e.deptno = d2.deptno\n"
+ "GROUP BY e.deptno";
sql(sql).with(program)
.check();
}
/** Similar to {@link #testAggregateJoinRemove1()};
* Should remove the bottom join since the project uses column in the
* right input of bottom join. */
@Test void testProjectJoinRemove1() {
final String sql = "SELECT e.deptno, d2.deptno\n"
+ "FROM sales.emp e\n"
+ "LEFT JOIN sales.dept d1 ON e.deptno = d1.deptno\n"
+ "LEFT JOIN sales.dept d2 ON e.deptno = d2.deptno";
sql(sql).withRule(ProjectJoinJoinRemoveRule.INSTANCE)
.check();
}
/** Similar to {@link #testAggregateJoinRemove1()};
* Should not remove the bottom join since the project uses column in the
* left input of bottom join. */
@Test void testProjectJoinRemove2() {
final String sql = "SELECT e.deptno, d1.deptno\n"
+ "FROM sales.emp e\n"
+ "LEFT JOIN sales.dept d1 ON e.deptno = d1.deptno\n"
+ "LEFT JOIN sales.dept d2 ON e.deptno = d2.deptno";
sql(sql).withRule(ProjectJoinJoinRemoveRule.INSTANCE)
.checkUnchanged();
}
/** Similar to {@link #testAggregateJoinRemove1()};
* Should not remove the bottom join since the right join keys of bottom
* join are not unique. */
@Test void testProjectJoinRemove3() {
final String sql = "SELECT e1.deptno, d.deptno\n"
+ "FROM sales.emp e1\n"
+ "LEFT JOIN sales.emp e2 ON e1.deptno = e2.deptno\n"
+ "LEFT JOIN sales.dept d ON e1.deptno = d.deptno";
sql(sql).withRule(ProjectJoinJoinRemoveRule.INSTANCE)
.checkUnchanged();
}
/** Similar to {@link #testAggregateJoinRemove1()};
* Should remove the left join since the join key of the right input is
* unique. */
@Test void testProjectJoinRemove4() {
final String sql = "SELECT e.deptno\n"
+ "FROM sales.emp e\n"
+ "LEFT JOIN sales.dept d ON e.deptno = d.deptno";
sql(sql).withRule(ProjectJoinRemoveRule.INSTANCE)
.check();
}
/** Similar to {@link #testAggregateJoinRemove1()};
* Should not remove the left join since the join key of the right input is
* not unique. */
@Test void testProjectJoinRemove5() {
final String sql = "SELECT e1.deptno\n"
+ "FROM sales.emp e1\n"
+ "LEFT JOIN sales.emp e2 ON e1.deptno = e2.deptno";
sql(sql).withRule(ProjectJoinRemoveRule.INSTANCE)
.checkUnchanged();
}
/** Similar to {@link #testAggregateJoinRemove1()};
* Should not remove the left join since the project use columns in the right
* input of the join. */
@Test void testProjectJoinRemove6() {
final String sql = "SELECT e.deptno, d.name\n"
+ "FROM sales.emp e\n"
+ "LEFT JOIN sales.dept d ON e.deptno = d.deptno";
sql(sql).withRule(ProjectJoinRemoveRule.INSTANCE)
.checkUnchanged();
}
/** Similar to {@link #testAggregateJoinRemove1()};
* Should remove the right join since the join key of the left input is
* unique. */
@Test void testProjectJoinRemove7() {
final String sql = "SELECT e.deptno\n"
+ "FROM sales.dept d\n"
+ "RIGHT JOIN sales.emp e ON e.deptno = d.deptno";
sql(sql).withRule(ProjectJoinRemoveRule.INSTANCE)
.check();
}
/** Similar to {@link #testAggregateJoinRemove1()};
* Should not remove the right join since the join key of the left input is
* not unique. */
@Test void testProjectJoinRemove8() {
final String sql = "SELECT e2.deptno\n"
+ "FROM sales.emp e1\n"
+ "RIGHT JOIN sales.emp e2 ON e1.deptno = e2.deptno";
sql(sql).withRule(ProjectJoinRemoveRule.INSTANCE)
.checkUnchanged();
}
/** Similar to {@link #testAggregateJoinRemove1()};
* Should not remove the right join since the project uses columns in the
* left input of the join. */
@Test void testProjectJoinRemove9() {
final String sql = "SELECT e.deptno, d.name\n"
+ "FROM sales.dept d\n"
+ "RIGHT JOIN sales.emp e ON e.deptno = d.deptno";
sql(sql).withRule(ProjectJoinRemoveRule.INSTANCE)
.checkUnchanged();
}
@Test void testSwapOuterJoin() {
final HepProgram program = new HepProgramBuilder()
.addMatchLimit(1)
.addRuleInstance(JoinCommuteRule.SWAP_OUTER)
.build();
final String sql = "select 1 from sales.dept d left outer join sales.emp e\n"
+ " on d.deptno = e.deptno";
sql(sql).with(program).check();
}
@Test void testPushJoinCondDownToProject() {
final String sql = "select d.deptno, e.deptno from sales.dept d, sales.emp e\n"
+ " where d.deptno + 10 = e.deptno * 2";
sql(sql).withRule(FilterJoinRule.FILTER_ON_JOIN,
JoinPushExpressionsRule.INSTANCE)
.check();
}
@Test void testSortJoinTranspose1() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(SortProjectTransposeRule.INSTANCE)
.build();
final String sql = "select * from sales.emp e left join (\n"
+ " select * from sales.dept d) d on e.deptno = d.deptno\n"
+ "order by sal limit 10";
sql(sql).withPre(preProgram)
.withRule(SortJoinTransposeRule.INSTANCE)
.check();
}
@Test void testSortJoinTranspose2() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(SortProjectTransposeRule.INSTANCE)
.build();
final String sql = "select * from sales.emp e right join (\n"
+ " select * from sales.dept d) d on e.deptno = d.deptno\n"
+ "order by name";
sql(sql).withPre(preProgram)
.withRule(SortJoinTransposeRule.INSTANCE)
.check();
}
@Test void testSortJoinTranspose3() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(SortProjectTransposeRule.INSTANCE)
.build();
// This one cannot be pushed down
final String sql = "select * from sales.emp e left join (\n"
+ " select * from sales.dept) d on e.deptno = d.deptno\n"
+ "order by sal, name limit 10";
sql(sql).withPre(preProgram)
.withRule(SortJoinTransposeRule.INSTANCE)
.checkUnchanged();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-931">[CALCITE-931]
* Wrong collation trait in SortJoinTransposeRule for right joins</a>. */
@Test void testSortJoinTranspose4() {
// Create a customized test with RelCollation trait in the test cluster.
Tester tester = new TesterImpl(getDiffRepos(), true, true, false, false,
true, null, null) {
@Override public RelOptPlanner createPlanner() {
return new MockRelOptPlanner(Contexts.empty()) {
@Override public List<RelTraitDef> getRelTraitDefs() {
return ImmutableList.of(RelCollationTraitDef.INSTANCE);
}
@Override public RelTraitSet emptyTraitSet() {
return RelTraitSet.createEmpty().plus(
RelCollationTraitDef.INSTANCE.getDefault());
}
};
}
};
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(SortProjectTransposeRule.INSTANCE)
.build();
final String sql = "select * from sales.emp e right join (\n"
+ " select * from sales.dept d) d on e.deptno = d.deptno\n"
+ "order by name";
sql(sql).withTester(t -> tester)
.withPre(preProgram)
.withRule(SortJoinTransposeRule.INSTANCE)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1498">[CALCITE-1498]
* Avoid LIMIT with trivial ORDER BY being pushed through JOIN endlessly</a>. */
@Test void testSortJoinTranspose5() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(SortProjectTransposeRule.INSTANCE)
.addRuleInstance(SortJoinTransposeRule.INSTANCE)
.addRuleInstance(SortProjectTransposeRule.INSTANCE)
.build();
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(SortJoinTransposeRule.INSTANCE)
.build();
// SortJoinTransposeRule should not be fired again.
final String sql = "select * from sales.emp e right join (\n"
+ " select * from sales.dept d) d on e.deptno = d.deptno\n"
+ "limit 10";
sql(sql).withPre(preProgram)
.with(program)
.checkUnchanged();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1507">[CALCITE-1507]
* OFFSET cannot be pushed through a JOIN if the non-preserved side of outer
* join is not count-preserving</a>. */
@Test void testSortJoinTranspose6() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(SortProjectTransposeRule.INSTANCE)
.build();
// This one can be pushed down even if it has an OFFSET, since the dept
// table is count-preserving against the join condition.
final String sql = "select d.deptno, empno from sales.dept d\n"
+ "right join sales.emp e using (deptno) limit 10 offset 2";
sql(sql)
.withPre(preProgram)
.withRule(SortJoinTransposeRule.INSTANCE)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1507">[CALCITE-1507]
* OFFSET cannot be pushed through a JOIN if the non-preserved side of outer
* join is not count-preserving</a>. */
@Test void testSortJoinTranspose7() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(SortProjectTransposeRule.INSTANCE)
.build();
// This one cannot be pushed down
final String sql = "select d.deptno, empno from sales.dept d\n"
+ "left join sales.emp e using (deptno) order by d.deptno offset 1";
sql(sql)
.withPre(preProgram)
.withRule(SortJoinTransposeRule.INSTANCE)
.checkUnchanged();
}
@Test void testSortProjectTranspose1() {
// This one can be pushed down
final String sql = "select d.deptno from sales.dept d\n"
+ "order by cast(d.deptno as integer) offset 1";
sql(sql).withRule(SortProjectTransposeRule.INSTANCE)
.check();
}
@Test void testSortProjectTranspose2() {
// This one can be pushed down
final String sql = "select d.deptno from sales.dept d\n"
+ "order by cast(d.deptno as double) offset 1";
sql(sql).withRule(SortProjectTransposeRule.INSTANCE)
.check();
}
@Test void testSortProjectTranspose3() {
// This one cannot be pushed down
final String sql = "select d.deptno from sales.dept d\n"
+ "order by cast(d.deptno as varchar(10)) offset 1";
sql(sql).withRule(SortJoinTransposeRule.INSTANCE)
.checkUnchanged();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1023">[CALCITE-1023]
* Planner rule that removes Aggregate keys that are constant</a>. */
@Test void testAggregateConstantKeyRule() {
final String sql = "select count(*) as c\n"
+ "from sales.emp\n"
+ "where deptno = 10\n"
+ "group by deptno, sal";
sql(sql).withRule(AggregateProjectPullUpConstantsRule.INSTANCE2)
.check();
}
/** Tests {@link AggregateProjectPullUpConstantsRule} where reduction is not
* possible because "deptno" is the only key. */
@Test void testAggregateConstantKeyRule2() {
final String sql = "select count(*) as c\n"
+ "from sales.emp\n"
+ "where deptno = 10\n"
+ "group by deptno";
sql(sql).withRule(AggregateProjectPullUpConstantsRule.INSTANCE2)
.checkUnchanged();
}
/** Tests {@link AggregateProjectPullUpConstantsRule} where both keys are
* constants but only one can be removed. */
@Test void testAggregateConstantKeyRule3() {
final String sql = "select job\n"
+ "from sales.emp\n"
+ "where sal is null and job = 'Clerk'\n"
+ "group by sal, job\n"
+ "having count(*) > 3";
sql(sql).withRule(AggregateProjectPullUpConstantsRule.INSTANCE2)
.check();
}
/** Tests {@link AggregateProjectPullUpConstantsRule} where
* there are group keys of type
* {@link org.apache.calcite.sql.fun.SqlAbstractTimeFunction}
* that can not be removed. */
@Test void testAggregateDynamicFunction() {
final String sql = "select hiredate\n"
+ "from sales.emp\n"
+ "where sal is null and hiredate = current_timestamp\n"
+ "group by sal, hiredate\n"
+ "having count(*) > 3";
sql(sql).withRule(AggregateProjectPullUpConstantsRule.INSTANCE2)
.check();
}
@Test void testReduceExpressionsNot() {
final String sql = "select * from (values (false),(true)) as q (col1) where not(col1)";
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE)
.checkUnchanged();
}
private Sql checkSubQuery(String sql) {
return sql(sql).withRule(SubQueryRemoveRule.PROJECT,
SubQueryRemoveRule.FILTER,
SubQueryRemoveRule.JOIN)
.expand(false);
}
/** Tests expanding a sub-query, specifically an uncorrelated scalar
* sub-query in a project (SELECT clause). */
@Test void testExpandProjectScalar() throws Exception {
final String sql = "select empno,\n"
+ " (select deptno from sales.emp where empno < 20) as d\n"
+ "from sales.emp";
checkSubQuery(sql).check();
}
@Test void testSelectNotInCorrelated() {
final String sql = "select sal,\n"
+ " empno NOT IN (\n"
+ " select deptno from dept\n"
+ " where emp.job=dept.name)\n"
+ " from emp";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1493">[CALCITE-1493]
* Wrong plan for NOT IN correlated queries</a>. */
@Test void testWhereNotInCorrelated() {
final String sql = "select sal from emp\n"
+ "where empno NOT IN (\n"
+ " select deptno from dept\n"
+ " where emp.job = dept.name)";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
@Test void testWhereNotInCorrelated2() {
final String sql = "select * from emp e1\n"
+ " where e1.empno NOT IN\n"
+ " (select empno from (select ename, empno, sal as r from emp) e2\n"
+ " where r > 2 and e1.ename= e2.ename)";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
@Test void testAll() {
final String sql = "select * from emp e1\n"
+ " where e1.empno > ALL (select deptno from dept)";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
@Test void testSome() {
final String sql = "select * from emp e1\n"
+ " where e1.empno > SOME (select deptno from dept)";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
/** Test case for testing type created by SubQueryRemoveRule: an
* ANY sub-query is non-nullable therefore plan should have cast. */
@Test void testAnyInProjectNonNullable() {
final String sql = "select name, deptno > ANY (\n"
+ " select deptno from emp)\n"
+ "from dept";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
/** Test case for testing type created by SubQueryRemoveRule; an
* ANY sub-query is nullable therefore plan should not have cast. */
@Test void testAnyInProjectNullable() {
final String sql = "select deptno, name = ANY (\n"
+ " select mgr from emp)\n"
+ "from dept";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
@Test void testSelectAnyCorrelated() {
final String sql = "select empno > ANY (\n"
+ " select deptno from dept where emp.job = dept.name)\n"
+ "from emp\n";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
@Test void testWhereAnyCorrelatedInSelect() {
final String sql =
"select * from emp where empno > ANY (\n"
+ " select deptno from dept where emp.job = dept.name)\n";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
@Test void testSomeWithEquality() {
final String sql = "select * from emp e1\n"
+ " where e1.deptno = SOME (select deptno from dept)";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
@Test void testSomeWithEquality2() {
final String sql = "select * from emp e1\n"
+ " where e1.ename= SOME (select name from dept)";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1546">[CALCITE-1546]
* Sub-queries connected by OR</a>. */
@Test void testWhereOrSubQuery() {
final String sql = "select * from emp\n"
+ "where sal = 4\n"
+ "or empno NOT IN (select deptno from dept)";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
@Test void testExpandProjectIn() throws Exception {
final String sql = "select empno,\n"
+ " deptno in (select deptno from sales.emp where empno < 20) as d\n"
+ "from sales.emp";
checkSubQuery(sql)
.withProperty(Hook.REL_BUILDER_SIMPLIFY, false)
.check();
}
@Test void testExpandProjectInNullable() throws Exception {
final String sql = "with e2 as (\n"
+ " select empno, case when true then deptno else null end as deptno\n"
+ " from sales.emp)\n"
+ "select empno,\n"
+ " deptno in (select deptno from e2 where empno < 20) as d\n"
+ "from e2";
checkSubQuery(sql)
.withProperty(Hook.REL_BUILDER_SIMPLIFY, false)
.check();
}
@Test void testExpandProjectInComposite() throws Exception {
final String sql = "select empno, (empno, deptno) in (\n"
+ " select empno, deptno from sales.emp where empno < 20) as d\n"
+ "from sales.emp";
checkSubQuery(sql)
.withProperty(Hook.REL_BUILDER_SIMPLIFY, false)
.check();
}
@Test void testExpandProjectExists() throws Exception {
final String sql = "select empno,\n"
+ " exists (select deptno from sales.emp where empno < 20) as d\n"
+ "from sales.emp";
checkSubQuery(sql)
.withProperty(Hook.REL_BUILDER_SIMPLIFY, false)
.check();
}
@Test void testExpandFilterScalar() throws Exception {
final String sql = "select empno\n"
+ "from sales.emp\n"
+ "where (select deptno from sales.emp where empno < 20)\n"
+ " < (select deptno from sales.emp where empno > 100)\n"
+ "or emp.sal < 100";
checkSubQuery(sql).check();
}
@Test void testExpandFilterIn() throws Exception {
final String sql = "select empno\n"
+ "from sales.emp\n"
+ "where deptno in (select deptno from sales.emp where empno < 20)\n"
+ "or emp.sal < 100";
checkSubQuery(sql).check();
}
@Test void testExpandFilterInComposite() throws Exception {
final String sql = "select empno\n"
+ "from sales.emp\n"
+ "where (empno, deptno) in (\n"
+ " select empno, deptno from sales.emp where empno < 20)\n"
+ "or emp.sal < 100";
checkSubQuery(sql).check();
}
/** An IN filter that requires full 3-value logic (true, false, unknown). */
@Test void testExpandFilterIn3Value() throws Exception {
final String sql = "select empno\n"
+ "from sales.emp\n"
+ "where empno\n"
+ " < case deptno in (select case when true then deptno else null end\n"
+ " from sales.emp where empno < 20)\n"
+ " when true then 10\n"
+ " when false then 20\n"
+ " else 30\n"
+ " end";
checkSubQuery(sql)
.withProperty(Hook.REL_BUILDER_SIMPLIFY, false)
.check();
}
/** An EXISTS filter that can be converted into true/false. */
@Test void testExpandFilterExists() throws Exception {
final String sql = "select empno\n"
+ "from sales.emp\n"
+ "where exists (select deptno from sales.emp where empno < 20)\n"
+ "or emp.sal < 100";
checkSubQuery(sql).check();
}
/** An EXISTS filter that can be converted into a semi-join. */
@Test void testExpandFilterExistsSimple() throws Exception {
final String sql = "select empno\n"
+ "from sales.emp\n"
+ "where exists (select deptno from sales.emp where empno < 20)";
checkSubQuery(sql).check();
}
/** An EXISTS filter that can be converted into a semi-join. */
@Test void testExpandFilterExistsSimpleAnd() throws Exception {
final String sql = "select empno\n"
+ "from sales.emp\n"
+ "where exists (select deptno from sales.emp where empno < 20)\n"
+ "and emp.sal < 100";
checkSubQuery(sql).check();
}
@Test void testExpandJoinScalar() throws Exception {
final String sql = "select empno\n"
+ "from sales.emp left join sales.dept\n"
+ "on (select deptno from sales.emp where empno < 20)\n"
+ " < (select deptno from sales.emp where empno > 100)";
checkSubQuery(sql).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3121">[CALCITE-3121]
* VolcanoPlanner hangs due to sub-query with dynamic star</a>. */
@Test void testSubQueryWithDynamicStarHang() {
String sql = "select n.n_regionkey from (select * from "
+ "(select * from sales.customer) t) n where n.n_nationkey >1";
VolcanoPlanner planner = new VolcanoPlanner(null, null);
planner.addRelTraitDef(ConventionTraitDef.INSTANCE);
Tester dynamicTester = createDynamicTester().withDecorrelation(true)
.withClusterFactory(
relOptCluster -> RelOptCluster.create(planner, relOptCluster.getRexBuilder()));
RelRoot root = dynamicTester.convertSqlToRel(sql);
String planBefore = NL + RelOptUtil.toString(root.rel);
getDiffRepos().assertEquals("planBefore", "${planBefore}", planBefore);
PushProjector.ExprCondition exprCondition = expr -> {
if (expr instanceof RexCall) {
RexCall call = (RexCall) expr;
return "item".equals(call.getOperator().getName().toLowerCase(Locale.ROOT));
}
return false;
};
RuleSet ruleSet =
RuleSets.ofList(
FilterProjectTransposeRule.INSTANCE,
FilterMergeRule.INSTANCE,
ProjectMergeRule.INSTANCE,
new ProjectFilterTransposeRule(Project.class, Filter .class,
RelFactories.LOGICAL_BUILDER, exprCondition),
EnumerableRules.ENUMERABLE_PROJECT_RULE,
EnumerableRules.ENUMERABLE_FILTER_RULE,
EnumerableRules.ENUMERABLE_SORT_RULE,
EnumerableRules.ENUMERABLE_LIMIT_RULE,
EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
Program program = Programs.of(ruleSet);
RelTraitSet toTraits =
root.rel.getCluster().traitSet()
.replace(0, EnumerableConvention.INSTANCE);
RelNode relAfter = program.run(planner, root.rel, toTraits,
Collections.emptyList(), Collections.emptyList());
String planAfter = NL + RelOptUtil.toString(relAfter);
getDiffRepos().assertEquals("planAfter", "${planAfter}", planAfter);
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3188">[CALCITE-3188]
* IndexOutOfBoundsException in ProjectFilterTransposeRule when executing SELECT COUNT(*)</a>. */
@Test void testProjectFilterTransposeRuleOnEmptyRowType() {
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
// build a rel equivalent to sql:
// select `empty` from emp
// where emp.deptno = 20
RelNode relNode = relBuilder.scan("EMP")
.filter(relBuilder
.equals(
relBuilder.field(1, 0, "DEPTNO"),
relBuilder.literal(20)))
.project(ImmutableList.of())
.build();
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ProjectFilterTransposeRule.INSTANCE)
.build();
HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
RelNode output = hepPlanner.findBestExp();
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
SqlToRelTestBase.assertValid(output);
}
@Disabled("[CALCITE-1045]")
@Test void testExpandJoinIn() throws Exception {
final String sql = "select empno\n"
+ "from sales.emp left join sales.dept\n"
+ "on emp.deptno in (select deptno from sales.emp where empno < 20)";
checkSubQuery(sql).check();
}
@Disabled("[CALCITE-1045]")
@Test void testExpandJoinInComposite() throws Exception {
final String sql = "select empno\n"
+ "from sales.emp left join sales.dept\n"
+ "on (emp.empno, dept.deptno) in (\n"
+ " select empno, deptno from sales.emp where empno < 20)";
checkSubQuery(sql).check();
}
@Test void testExpandJoinExists() throws Exception {
final String sql = "select empno\n"
+ "from sales.emp left join sales.dept\n"
+ "on exists (select deptno from sales.emp where empno < 20)";
checkSubQuery(sql).check();
}
@Test void testDecorrelateExists() throws Exception {
final String sql = "select * from sales.emp\n"
+ "where EXISTS (\n"
+ " select * from emp e where emp.deptno = e.deptno)";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1511">[CALCITE-1511]
* AssertionError while decorrelating query with two EXISTS
* sub-queries</a>. */
@Test void testDecorrelateTwoExists() throws Exception {
final String sql = "select * from sales.emp\n"
+ "where EXISTS (\n"
+ " select * from emp e where emp.deptno = e.deptno)\n"
+ "AND NOT EXISTS (\n"
+ " select * from emp ee where ee.job = emp.job AND ee.sal=34)";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2028">[CALCITE-2028]
* Un-correlated IN sub-query should be converted into a Join,
* rather than a Correlate without correlation variables </a>. */
@Test void testDecorrelateUncorrelatedInAndCorrelatedExists() throws Exception {
final String sql = "select * from sales.emp\n"
+ "WHERE job in (\n"
+ " select job from emp ee where ee.sal=34)"
+ "AND EXISTS (\n"
+ " select * from emp e where emp.deptno = e.deptno)\n";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1537">[CALCITE-1537]
* Unnecessary project expression in multi-sub-query plan</a>. */
@Test void testDecorrelateTwoIn() throws Exception {
final String sql = "select sal\n"
+ "from sales.emp\n"
+ "where empno IN (\n"
+ " select deptno from dept where emp.job = dept.name)\n"
+ "AND empno IN (\n"
+ " select empno from emp e where emp.ename = e.ename)";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1045">[CALCITE-1045]
* Decorrelate sub-queries in Project and Join</a>, with the added
* complication that there are two sub-queries. */
@Disabled("[CALCITE-1045]")
@Test void testDecorrelateTwoScalar() throws Exception {
final String sql = "select deptno,\n"
+ " (select min(1) from emp where empno > d.deptno) as i0,\n"
+ " (select min(0) from emp\n"
+ " where deptno = d.deptno and ename = 'SMITH') as i1\n"
+ "from dept as d";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
@Test void testWhereInJoinCorrelated() {
final String sql = "select empno from emp as e\n"
+ "join dept as d using (deptno)\n"
+ "where e.sal in (\n"
+ " select e2.sal from emp as e2 where e2.deptno > e.deptno)";
checkSubQuery(sql).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1494">[CALCITE-1494]
* Inefficient plan for correlated sub-queries</a>. In "planAfter", there
* must be only one scan each of emp and dept. We don't need a separate
* value-generator for emp.job. */
@Test void testWhereInCorrelated() {
final String sql = "select sal from emp where empno IN (\n"
+ " select deptno from dept where emp.job = dept.name)";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
@Test void testWhereExpressionInCorrelated() {
final String sql = "select ename from (\n"
+ " select ename, deptno, sal + 1 as salPlus from emp) as e\n"
+ "where deptno in (\n"
+ " select deptno from emp where sal + 1 = e.salPlus)";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
@Test void testWhereExpressionInCorrelated2() {
final String sql = "select name from (\n"
+ " select name, deptno, deptno - 10 as deptnoMinus from dept) as d\n"
+ "where deptno in (\n"
+ " select deptno from emp where sal + 1 = d.deptnoMinus)";
checkSubQuery(sql).withLateDecorrelation(true).check();
}
@Test void testExpandWhereComparisonCorrelated() throws Exception {
final String sql = "select empno\n"
+ "from sales.emp as e\n"
+ "where sal = (\n"
+ " select max(sal) from sales.emp e2 where e2.empno = e.empno)";
checkSubQuery(sql).check();
}
@Test void testCustomColumnResolvingInNonCorrelatedSubQuery() {
final String sql = "select *\n"
+ "from struct.t t1\n"
+ "where c0 in (\n"
+ " select f1.c0 from struct.t t2)";
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(SubQueryRemoveRule.PROJECT)
.addRuleInstance(SubQueryRemoveRule.FILTER)
.addRuleInstance(SubQueryRemoveRule.JOIN)
.build();
sql(sql)
.withTrim(true)
.expand(false)
.with(program)
.check();
}
@Test void testCustomColumnResolvingInCorrelatedSubQuery() {
final String sql = "select *\n"
+ "from struct.t t1\n"
+ "where c0 = (\n"
+ " select max(f1.c0) from struct.t t2 where t1.k0 = t2.k0)";
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(SubQueryRemoveRule.PROJECT)
.addRuleInstance(SubQueryRemoveRule.FILTER)
.addRuleInstance(SubQueryRemoveRule.JOIN)
.build();
sql(sql)
.withTrim(true)
.expand(false)
.with(program)
.check();
}
@Test void testCustomColumnResolvingInCorrelatedSubQuery2() {
final String sql = "select *\n"
+ "from struct.t t1\n"
+ "where c0 in (\n"
+ " select f1.c0 from struct.t t2 where t1.c2 = t2.c2)";
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(SubQueryRemoveRule.PROJECT)
.addRuleInstance(SubQueryRemoveRule.FILTER)
.addRuleInstance(SubQueryRemoveRule.JOIN)
.build();
sql(sql)
.withTrim(true)
.expand(false)
.with(program)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2744">[CALCITE-2744]
* RelDecorrelator use wrong output map for LogicalAggregate decorrelate</a>. */
@Test void testDecorrelateAggWithConstantGroupKey() {
final String sql = "SELECT * FROM emp A where sal in\n"
+ "(SELECT max(sal) FROM emp B where A.mgr = B.empno group by deptno, 'abc')";
sql(sql)
.withLateDecorrelation(true)
.withTrim(true)
.with(HepProgram.builder().build())
.check();
}
/** Test case for CALCITE-2744 for aggregate decorrelate with multi-param agg call
* but without group key. */
@Test void testDecorrelateAggWithMultiParamsAggCall() {
final String sql = "SELECT * FROM (SELECT MYAGG(sal, 1) AS c FROM emp) as m,\n"
+ " LATERAL TABLE(ramp(m.c)) AS T(s)";
sql(sql)
.withLateDecorrelation(true)
.withTrim(true)
.with(HepProgram.builder().build())
.checkUnchanged();
}
/** Same as {@link #testDecorrelateAggWithMultiParamsAggCall}
* but with a constant group key. */
@Test void testDecorrelateAggWithMultiParamsAggCall2() {
final String sql = "SELECT * FROM "
+ "(SELECT MYAGG(sal, 1) AS c FROM emp group by empno, 'abc') as m,\n"
+ " LATERAL TABLE(ramp(m.c)) AS T(s)";
sql(sql)
.withLateDecorrelation(true)
.withTrim(true)
.with(HepProgram.builder().build())
.checkUnchanged();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-434">[CALCITE-434]
* Converting predicates on date dimension columns into date ranges</a>,
* specifically a rule that converts {@code EXTRACT(YEAR FROM ...) = constant}
* to a range. */
@Test void testExtractYearToRange() {
final String sql = "select *\n"
+ "from sales.emp_b as e\n"
+ "where extract(year from birthdate) = 2014";
final Context context =
Contexts.of(new CalciteConnectionConfigImpl(new Properties()));
sql(sql).withRule(DateRangeRules.FILTER_INSTANCE)
.withContext(context)
.check();
}
@Test void testExtractYearMonthToRange() {
final String sql = "select *\n"
+ "from sales.emp_b as e\n"
+ "where extract(year from birthdate) = 2014"
+ "and extract(month from birthdate) = 4";
final Context context =
Contexts.of(new CalciteConnectionConfigImpl(new Properties()));
sql(sql).withRule(DateRangeRules.FILTER_INSTANCE)
.withContext(context)
.check();
}
@Test void testFilterRemoveIsNotDistinctFromRule() {
final DiffRepository diffRepos = getDiffRepos();
final RelBuilder builder = RelBuilder.create(RelBuilderTest.config().build());
RelNode root = builder
.scan("EMP")
.filter(
builder.call(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
builder.field("DEPTNO"), builder.literal(20)))
.build();
HepProgram preProgram = new HepProgramBuilder().build();
HepPlanner prePlanner = new HepPlanner(preProgram);
prePlanner.setRoot(root);
final RelNode relBefore = prePlanner.findBestExp();
final String planBefore = NL + RelOptUtil.toString(relBefore);
diffRepos.assertEquals("planBefore", "${planBefore}", planBefore);
HepProgram hepProgram = new HepProgramBuilder()
.addRuleInstance(FilterRemoveIsNotDistinctFromRule.INSTANCE)
.build();
HepPlanner hepPlanner = new HepPlanner(hepProgram);
hepPlanner.setRoot(root);
final RelNode relAfter = hepPlanner.findBestExp();
final String planAfter = NL + RelOptUtil.toString(relAfter);
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
}
@Test void testOversimplifiedCaseStatement() {
String sql = "select * from emp "
+ "where MGR > 0 and "
+ "case when MGR > 0 then deptno / MGR else null end > 1";
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2726">[CALCITE-2726]
* ReduceExpressionRule may oversimplify filter conditions containing nulls</a>.
*/
@Test void testNoOversimplificationBelowIsNull() {
String sql =
"select * from emp where ( (empno=1 and mgr=1) or (empno=null and mgr=1) ) is null";
sql(sql).withRule(ReduceExpressionsRule.FILTER_INSTANCE)
.check();
}
@Test void testExchangeRemoveConstantKeysRule() {
final DiffRepository diffRepos = getDiffRepos();
final RelBuilder builder = RelBuilder.create(RelBuilderTest.config().build());
RelNode root = builder
.scan("EMP")
.filter(
builder.call(SqlStdOperatorTable.EQUALS,
builder.field("EMPNO"), builder.literal(10)))
.exchange(RelDistributions.hash(ImmutableList.of(0)))
.project(builder.field(0), builder.field(1))
.sortExchange(RelDistributions.hash(ImmutableList.of(0, 1)),
RelCollations.of(new RelFieldCollation(0), new RelFieldCollation(1)))
.build();
HepProgram preProgram = new HepProgramBuilder().build();
HepPlanner prePlanner = new HepPlanner(preProgram);
prePlanner.setRoot(root);
final RelNode relBefore = prePlanner.findBestExp();
final String planBefore = NL + RelOptUtil.toString(relBefore);
diffRepos.assertEquals("planBefore", "${planBefore}", planBefore);
HepProgram hepProgram = new HepProgramBuilder()
.addRuleInstance(ExchangeRemoveConstantKeysRule.EXCHANGE_INSTANCE)
.addRuleInstance(ExchangeRemoveConstantKeysRule.SORT_EXCHANGE_INSTANCE)
.build();
HepPlanner hepPlanner = new HepPlanner(hepProgram);
hepPlanner.setRoot(root);
final RelNode relAfter = hepPlanner.findBestExp();
final String planAfter = NL + RelOptUtil.toString(relAfter);
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
}
@Test void testReduceAverageWithNoReduceSum() {
final EnumSet<SqlKind> functionsToReduce = EnumSet.of(SqlKind.AVG);
final RelOptRule rule = new AggregateReduceFunctionsRule(LogicalAggregate.class,
RelFactories.LOGICAL_BUILDER, functionsToReduce);
final String sql = "select name, max(name), avg(deptno), min(name)\n"
+ "from sales.dept group by name";
sql(sql).withRule(rule).check();
}
@Test void testNoReduceAverage() {
final EnumSet<SqlKind> functionsToReduce = EnumSet.noneOf(SqlKind.class);
final RelOptRule rule = new AggregateReduceFunctionsRule(LogicalAggregate.class,
RelFactories.LOGICAL_BUILDER, functionsToReduce);
String sql = "select name, max(name), avg(deptno), min(name)"
+ " from sales.dept group by name";
sql(sql).withRule(rule).checkUnchanged();
}
@Test void testNoReduceSum() {
final EnumSet<SqlKind> functionsToReduce = EnumSet.noneOf(SqlKind.class);
final RelOptRule rule = new AggregateReduceFunctionsRule(LogicalAggregate.class,
RelFactories.LOGICAL_BUILDER, functionsToReduce);
String sql = "select name, sum(deptno)"
+ " from sales.dept group by name";
sql(sql).withRule(rule).checkUnchanged();
}
@Test void testReduceAverageAndVarWithNoReduceStddev() {
// configure rule to reduce AVG and VAR_POP functions
// other functions like SUM, STDDEV won't be reduced
final EnumSet<SqlKind> functionsToReduce = EnumSet.of(SqlKind.AVG, SqlKind.VAR_POP);
final RelOptRule rule = new AggregateReduceFunctionsRule(LogicalAggregate.class,
RelFactories.LOGICAL_BUILDER, functionsToReduce);
final String sql = "select name, stddev_pop(deptno), avg(deptno),"
+ " var_pop(deptno)\n"
+ "from sales.dept group by name";
sql(sql).withRule(rule).check();
}
@Test void testReduceAverageAndSumWithNoReduceStddevAndVar() {
// configure rule to reduce AVG and SUM functions
// other functions like VAR_POP, STDDEV_POP won't be reduced
final EnumSet<SqlKind> functionsToReduce = EnumSet.of(SqlKind.AVG, SqlKind.SUM);
final RelOptRule rule = new AggregateReduceFunctionsRule(LogicalAggregate.class,
RelFactories.LOGICAL_BUILDER, functionsToReduce);
final String sql = "select name, stddev_pop(deptno), avg(deptno),"
+ " var_pop(deptno)\n"
+ "from sales.dept group by name";
sql(sql).withRule(rule).check();
}
@Test void testReduceAllAggregateFunctions() {
// configure rule to reduce all used functions
final EnumSet<SqlKind> functionsToReduce = EnumSet.of(SqlKind.AVG, SqlKind.SUM,
SqlKind.STDDEV_POP, SqlKind.STDDEV_SAMP, SqlKind.VAR_POP, SqlKind.VAR_SAMP);
final RelOptRule rule = new AggregateReduceFunctionsRule(LogicalAggregate.class,
RelFactories.LOGICAL_BUILDER, functionsToReduce);
final String sql = "select name, stddev_pop(deptno), avg(deptno),"
+ " stddev_samp(deptno), var_pop(deptno), var_samp(deptno)\n"
+ "from sales.dept group by name";
sql(sql).withRule(rule).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2803">[CALCITE-2803]
* Identify expanded IS NOT DISTINCT FROM expression when pushing project past join</a>.
*/
@Test void testPushProjectWithIsNotDistinctFromPastJoin() {
final String sql = "select e.sal + b.comm from emp e inner join bonus b\n"
+ "on (e.ename || e.job) IS NOT DISTINCT FROM (b.ename || b.job) and e.deptno = 10";
sql(sql).withRule(ProjectJoinTransposeRule.INSTANCE).check();
}
@Test void testDynamicStarWithUnion() {
String sql = "(select n_nationkey from SALES.CUSTOMER) union all\n"
+ "(select n_name from CUSTOMER_MODIFIABLEVIEW)";
VolcanoPlanner planner = new VolcanoPlanner(null, null);
planner.addRelTraitDef(ConventionTraitDef.INSTANCE);
Tester dynamicTester = createDynamicTester().withDecorrelation(true)
.withClusterFactory(
relOptCluster -> RelOptCluster.create(planner, relOptCluster.getRexBuilder()));
RelRoot root = dynamicTester.convertSqlToRel(sql);
String planBefore = NL + RelOptUtil.toString(root.rel);
getDiffRepos().assertEquals("planBefore", "${planBefore}", planBefore);
RuleSet ruleSet =
RuleSets.ofList(
EnumerableRules.ENUMERABLE_PROJECT_RULE,
EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE,
EnumerableRules.ENUMERABLE_UNION_RULE);
Program program = Programs.of(ruleSet);
RelTraitSet toTraits =
root.rel.getCluster().traitSet()
.replace(0, EnumerableConvention.INSTANCE);
RelNode relAfter = program.run(planner, root.rel, toTraits,
Collections.emptyList(), Collections.emptyList());
String planAfter = NL + RelOptUtil.toString(relAfter);
getDiffRepos().assertEquals("planAfter", "${planAfter}", planAfter);
}
@Test void testFilterAndProjectWithMultiJoin() throws Exception {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleCollection(Arrays.asList(MyFilterRule.INSTANCE, MyProjectRule.INSTANCE))
.build();
final FilterMultiJoinMergeRule filterMultiJoinMergeRule =
new FilterMultiJoinMergeRule(MyFilter.class, RelFactories.LOGICAL_BUILDER);
final ProjectMultiJoinMergeRule projectMultiJoinMergeRule =
new ProjectMultiJoinMergeRule(MyProject.class, RelFactories.LOGICAL_BUILDER);
HepProgram program = new HepProgramBuilder()
.addRuleCollection(
Arrays.asList(
JoinToMultiJoinRule.INSTANCE,
filterMultiJoinMergeRule,
projectMultiJoinMergeRule))
.build();
sql("select * from emp e1 left outer join dept d on e1.deptno = d.deptno where d.deptno > 3")
.withPre(preProgram).with(program).check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3151">[CALCITE-3151]
* RexCall's Monotonicity is not considered in determining a Calc's collation</a>
*/
@Test void testMonotonicityUDF() throws Exception {
final SqlFunction monotonicityFun =
new SqlFunction("MONOFUN", SqlKind.OTHER_FUNCTION, ReturnTypes.BIGINT, null,
OperandTypes.NILADIC, SqlFunctionCategory.USER_DEFINED_FUNCTION) {
@Override public boolean isDeterministic() {
return false;
}
@Override public SqlMonotonicity getMonotonicity(SqlOperatorBinding call) {
return SqlMonotonicity.INCREASING;
}
};
// Build a tree equivalent to the SQL
// SELECT sal, MONOFUN() AS n FROM emp
final RelBuilder builder =
RelBuilder.create(RelBuilderTest.config().build());
final RelNode root =
builder.scan("EMP")
.project(builder.field("SAL"),
builder.alias(builder.call(monotonicityFun), "M"))
.build();
HepProgram preProgram = new HepProgramBuilder().build();
HepPlanner prePlanner = new HepPlanner(preProgram);
prePlanner.setRoot(root);
final RelNode relBefore = prePlanner.findBestExp();
final RelCollation collationBefore =
relBefore.getTraitSet().getTrait(RelCollationTraitDef.INSTANCE);
HepProgram hepProgram = new HepProgramBuilder()
.addRuleInstance(ProjectToCalcRule.INSTANCE)
.build();
HepPlanner hepPlanner = new HepPlanner(hepProgram);
hepPlanner.setRoot(root);
final RelNode relAfter = hepPlanner.findBestExp();
final RelCollation collationAfter =
relAfter.getTraitSet().getTrait(RelCollationTraitDef.INSTANCE);
assertEquals(collationBefore, collationAfter);
}
@Test void testPushFiltertWithIsNotDistinctFromPastJoin() {
String query = "SELECT * FROM "
+ "emp t1 INNER JOIN "
+ "emp t2 "
+ "ON t1.deptno = t2.deptno "
+ "WHERE t1.ename is not distinct from t2.ename";
sql(query).withRule(FilterJoinRule.FILTER_ON_JOIN).check();
}
/**
* Custom implementation of {@link Filter} for use
* in test case to verify that {@link FilterMultiJoinMergeRule}
* can be created with any {@link Filter} and not limited to
* {@link org.apache.calcite.rel.logical.LogicalFilter}
*/
private static class MyFilter extends Filter {
MyFilter(
RelOptCluster cluster,
RelTraitSet traitSet,
RelNode child,
RexNode condition) {
super(cluster, traitSet, child, condition);
}
public MyFilter copy(RelTraitSet traitSet, RelNode input,
RexNode condition) {
return new MyFilter(getCluster(), traitSet, input, condition);
}
}
/**
* Rule to transform {@link LogicalFilter} into
* custom MyFilter
*/
private static class MyFilterRule extends RelOptRule {
static final MyFilterRule INSTANCE =
new MyFilterRule(LogicalFilter.class, RelFactories.LOGICAL_BUILDER);
private MyFilterRule(Class<? extends Filter> clazz,
RelBuilderFactory relBuilderFactory) {
super(RelOptRule.operand(clazz, RelOptRule.any()), relBuilderFactory, null);
}
@Override public void onMatch(RelOptRuleCall call) {
final LogicalFilter logicalFilter = call.rel(0);
final RelNode input = logicalFilter.getInput();
final MyFilter myFilter = new MyFilter(input.getCluster(), input.getTraitSet(), input,
logicalFilter.getCondition());
call.transformTo(myFilter);
}
}
/**
* Custom implementation of {@link Project} for use
* in test case to verify that {@link ProjectMultiJoinMergeRule}
* can be created with any {@link Project} and not limited to
* {@link org.apache.calcite.rel.logical.LogicalProject}
*/
private static class MyProject extends Project {
MyProject(
RelOptCluster cluster,
RelTraitSet traitSet,
RelNode input,
List<? extends RexNode> projects,
RelDataType rowType) {
super(cluster, traitSet, ImmutableList.of(), input, projects, rowType);
}
public MyProject copy(RelTraitSet traitSet, RelNode input,
List<RexNode> projects, RelDataType rowType) {
return new MyProject(getCluster(), traitSet, input, projects, rowType);
}
}
/**
* Rule to transform {@link LogicalProject} into custom
* MyProject
*/
private static class MyProjectRule extends RelOptRule {
static final MyProjectRule INSTANCE =
new MyProjectRule(LogicalProject.class, RelFactories.LOGICAL_BUILDER);
private MyProjectRule(Class<? extends Project> clazz,
RelBuilderFactory relBuilderFactory) {
super(RelOptRule.operand(clazz, RelOptRule.any()), relBuilderFactory, null);
}
@Override public void onMatch(RelOptRuleCall call) {
final LogicalProject logicalProject = call.rel(0);
final RelNode input = logicalProject.getInput();
final MyProject myProject = new MyProject(input.getCluster(), input.getTraitSet(), input,
logicalProject.getChildExps(), logicalProject.getRowType());
call.transformTo(myProject);
}
}
@Test void testSortJoinCopyInnerJoinOrderBy() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(SortProjectTransposeRule.INSTANCE)
.build();
final String sql = "select * from sales.emp join sales.dept on\n"
+ "sales.emp.deptno = sales.dept.deptno order by sal";
sql(sql).withPre(preProgram)
.withRule(SortJoinCopyRule.INSTANCE)
.check();
}
@Test void testSortJoinCopyInnerJoinOrderByLimit() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(SortProjectTransposeRule.INSTANCE)
.build();
final String sql = "select * from sales.emp e join (\n"
+ " select * from sales.dept d) d on e.deptno = d.deptno\n"
+ "order by sal limit 10";
sql(sql).withPre(preProgram)
.withRule(SortJoinCopyRule.INSTANCE)
.check();
}
@Test void testSortJoinCopyInnerJoinOrderByTwoFields() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(SortProjectTransposeRule.INSTANCE)
.build();
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(SortJoinCopyRule.INSTANCE)
.build();
final String sql = "select * from sales.emp e join sales.dept d on\n"
+ " e.deptno = d.deptno order by e.sal,d.name";
sql(sql).withPre(preProgram)
.withRule(SortJoinCopyRule.INSTANCE)
.check();
}
@Test void testSortJoinCopySemiJoinOrderBy() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(SemiJoinRule.PROJECT)
.build();
final String sql = "select * from sales.dept d where d.deptno in\n"
+ " (select e.deptno from sales.emp e) order by d.deptno";
sql(sql).withPre(preProgram)
.withRule(SortJoinCopyRule.INSTANCE)
.check();
}
@Test void testSortJoinCopySemiJoinOrderByLimitOffset() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(SemiJoinRule.PROJECT)
.build();
final String sql = "select * from sales.dept d where d.deptno in\n"
+ " (select e.deptno from sales.emp e) order by d.deptno limit 10 offset 2";
// Do not copy the limit and offset
sql(sql).withPre(preProgram)
.withRule(SortJoinCopyRule.INSTANCE)
.check();
}
@Test void testSortJoinCopySemiJoinOrderByOffset() {
final HepProgram preProgram = new HepProgramBuilder()
.addRuleInstance(SemiJoinRule.PROJECT)
.build();
final String sql = "select * from sales.dept d where d.deptno in"
+ " (select e.deptno from sales.emp e) order by d.deptno offset 2";
// Do not copy the offset
sql(sql).withPre(preProgram)
.withRule(SortJoinCopyRule.INSTANCE)
.check();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3296">[CALCITE-3296]
* Decorrelator gives empty result
* after decorrelating sort rel with null offset and fetch </a>
*/
@Test void testDecorrelationWithSort() {
final String sql = "SELECT e1.empno\n"
+ "FROM emp e1, dept d1 where e1.deptno = d1.deptno\n"
+ "and e1.deptno < 10 and d1.deptno < 15\n"
+ "and e1.sal > (select avg(sal) from emp e2 where e1.empno = e2.empno)\n"
+ "order by e1.empno";
sql(sql).with(HepProgram.builder().build())
.withDecorrelation(true)
.checkUnchanged();
}
/**
* Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3319">[CALCITE-3319]
* AssertionError for ReduceDecimalsRule </a>
*/
@Test void testReduceDecimal() {
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(FilterToCalcRule.INSTANCE)
.addRuleInstance(ReduceDecimalsRule.INSTANCE)
.build();
final String sql = "select ename from emp where sal > cast (100.0 as decimal(4, 1))";
sql(sql).with(program).check();
}
@Test void testEnumerableCalcRule() {
final String sql = "select FNAME, LNAME from SALES.CUSTOMER where CONTACTNO > 10";
VolcanoPlanner planner = new VolcanoPlanner(null, null);
planner.addRelTraitDef(ConventionTraitDef.INSTANCE);
planner.addRelTraitDef(RelDistributionTraitDef.INSTANCE);
Tester dynamicTester = createDynamicTester().withDecorrelation(true)
.withClusterFactory(
relOptCluster -> RelOptCluster.create(planner, relOptCluster.getRexBuilder()));
RelRoot root = dynamicTester.convertSqlToRel(sql);
String planBefore = NL + RelOptUtil.toString(root.rel);
getDiffRepos().assertEquals("planBefore", "${planBefore}", planBefore);
RuleSet ruleSet =
RuleSets.ofList(
FilterToCalcRule.INSTANCE,
EnumerableRules.ENUMERABLE_PROJECT_RULE,
EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE,
EnumerableRules.ENUMERABLE_CALC_RULE);
Program program = Programs.of(ruleSet);
RelTraitSet toTraits =
root.rel.getCluster().traitSet()
.replace(0, EnumerableConvention.INSTANCE);
RelNode relAfter = program.run(planner, root.rel, toTraits,
Collections.emptyList(), Collections.emptyList());
String planAfter = NL + RelOptUtil.toString(relAfter);
getDiffRepos().assertEquals("planAfter", "${planAfter}", planAfter);
}
/**
* Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3404">[CALCITE-3404]
* Treat agg expressions that can ignore distinct constraint as distinct
* in AggregateExpandDistinctAggregatesRule
* when all the other agg expressions are distinct and have same arguments</a>
*/
@Test void testMaxReuseDistinctAttrWithMixedOptionality() {
final String sql = "select sum(distinct deptno), count(distinct deptno), "
+ "max(deptno) from emp";
sql(sql).withRule(AggregateExpandDistinctAggregatesRule.INSTANCE).check();
}
@Test void testMinReuseDistinctAttrWithMixedOptionality() {
final String sql = "select sum(distinct deptno), count(distinct deptno), "
+ "min(deptno) from emp";
sql(sql).withRule(AggregateExpandDistinctAggregatesRule.INSTANCE).check();
}
@Test void testBitAndReuseDistinctAttrWithMixedOptionality() {
final String sql = "select sum(distinct deptno), count(distinct deptno), "
+ "bit_and(deptno) from emp";
sql(sql).withRule(AggregateExpandDistinctAggregatesRule.INSTANCE).check();
}
@Test void testBitOrReuseDistinctAttrWithMixedOptionality() {
final String sql = "select sum(distinct deptno), count(distinct deptno), "
+ "bit_or(deptno) from emp";
sql(sql).withRule(AggregateExpandDistinctAggregatesRule.INSTANCE).check();
}
@Test void testProjectJoinTransposeItem() {
ProjectJoinTransposeRule projectJoinTransposeRule =
new ProjectJoinTransposeRule(Project.class, Join.class, skipItem, RelFactories
.LOGICAL_BUILDER);
String query = "select t1.c_nationkey[0], t2.c_nationkey[0] "
+ "from sales.customer as t1 left outer join sales.customer as t2 "
+ "on t1.c_nationkey[0] = t2.c_nationkey[0]";
sql(query).withTester(t -> createDynamicTester()).withRule(projectJoinTransposeRule).check();
}
@Test void testSimplifyItemIsNotNull() {
String query = "select * from sales.customer as t1 where t1.c_nationkey[0] is not null";
sql(query)
.withTester(t -> createDynamicTester())
.withRule(ReduceExpressionsRule.FILTER_INSTANCE)
.checkUnchanged();
}
@Test void testSimplifyItemIsNull() {
String query = "select * from sales.customer as t1 where t1.c_nationkey[0] is null";
sql(query)
.withTester(t -> createDynamicTester())
.withRule(ReduceExpressionsRule.FILTER_INSTANCE)
.checkUnchanged();
}
}