You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2014/08/11 21:46:04 UTC
[7/8] git commit: [OPTIQ-367] PushFilterPastJoinRule should
strengthen join type
[OPTIQ-367] PushFilterPastJoinRule should strengthen join type
Make RexNode.isAlwaysTrue smart enough to deal with "c IS NOT NULL" on a column declared NOT NULL.
Change how EXISTS is translated (was "c IS TRUE", now "c IS NOT NULL", where c is a marker column from the null-generating side).
Keep a dumber version of PushFilterPastJoinRule around, to keep some tests working.
Project: http://git-wip-us.apache.org/repos/asf/incubator-optiq/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-optiq/commit/201d5917
Tree: http://git-wip-us.apache.org/repos/asf/incubator-optiq/tree/201d5917
Diff: http://git-wip-us.apache.org/repos/asf/incubator-optiq/diff/201d5917
Branch: refs/heads/master
Commit: 201d5917931bb5e2a455ad786a671764bc8b3833
Parents: 5a42f3f
Author: Julian Hyde <jh...@apache.org>
Authored: Sat Aug 9 02:33:00 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Sat Aug 9 02:33:00 2014 -0700
----------------------------------------------------------------------
.../java/org/eigenbase/rel/JoinRelType.java | 26 ++++
.../org/eigenbase/rel/metadata/RelMdUtil.java | 7 +-
.../rel/rules/PushFilterPastJoinRule.java | 99 +++++++++-----
.../java/org/eigenbase/relopt/RelOptUtil.java | 134 ++++++++++++++-----
.../main/java/org/eigenbase/rex/RexCall.java | 21 +++
.../eigenbase/sql2rel/SqlToRelConverter.java | 2 +-
.../org/eigenbase/test/RelMetadataTest.java | 8 +-
.../org/eigenbase/test/RelOptRulesTest.java | 43 ++++--
.../org/eigenbase/test/SqlToRelTestBase.java | 27 +++-
.../org/eigenbase/test/RelOptRulesTest.xml | 46 +++++++
.../eigenbase/test/SqlToRelConverterTest.xml | 14 +-
11 files changed, 335 insertions(+), 92 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/main/java/org/eigenbase/rel/JoinRelType.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/JoinRelType.java b/core/src/main/java/org/eigenbase/rel/JoinRelType.java
index 4c4339c..6c73c13 100644
--- a/core/src/main/java/org/eigenbase/rel/JoinRelType.java
+++ b/core/src/main/java/org/eigenbase/rel/JoinRelType.java
@@ -63,6 +63,32 @@ public enum JoinRelType {
throw new IllegalArgumentException("invalid: " + i);
}
}
+
+ /** Returns a join type similar to this but that does not generate nulls on
+ * the left. */
+ public JoinRelType cancelNullsOnLeft() {
+ switch (this) {
+ case RIGHT:
+ return INNER;
+ case FULL:
+ return LEFT;
+ default:
+ return this;
+ }
+ }
+
+ /** Returns a join type similar to this but that does not generate nulls on
+ * the right. */
+ public JoinRelType cancelNullsOnRight() {
+ switch (this) {
+ case LEFT:
+ return INNER;
+ case FULL:
+ return RIGHT;
+ default:
+ return this;
+ }
+ }
}
// End JoinRelType.java
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/main/java/org/eigenbase/rel/metadata/RelMdUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/metadata/RelMdUtil.java b/core/src/main/java/org/eigenbase/rel/metadata/RelMdUtil.java
index b409309..03e6302 100644
--- a/core/src/main/java/org/eigenbase/rel/metadata/RelMdUtil.java
+++ b/core/src/main/java/org/eigenbase/rel/metadata/RelMdUtil.java
@@ -25,6 +25,7 @@ import org.eigenbase.relopt.*;
import org.eigenbase.rex.*;
import org.eigenbase.sql.*;
import org.eigenbase.sql.type.*;
+import org.eigenbase.util.Holder;
import org.eigenbase.util14.*;
import net.hydromatic.optiq.util.BitSets;
@@ -660,12 +661,14 @@ public class RelMdUtil {
RelOptUtil.classifyFilters(
joinRel,
predList,
- joinType == JoinRelType.INNER,
+ joinType,
!joinType.generatesNullsOnLeft(),
!joinType.generatesNullsOnRight(),
joinFilters,
leftFilters,
- rightFilters);
+ rightFilters,
+ Holder.of(joinType),
+ false);
RexBuilder rexBuilder = joinRel.getCluster().getRexBuilder();
leftPred =
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/main/java/org/eigenbase/rel/rules/PushFilterPastJoinRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/PushFilterPastJoinRule.java b/core/src/main/java/org/eigenbase/rel/rules/PushFilterPastJoinRule.java
index 7514926..f97cda6 100644
--- a/core/src/main/java/org/eigenbase/rel/rules/PushFilterPastJoinRule.java
+++ b/core/src/main/java/org/eigenbase/rel/rules/PushFilterPastJoinRule.java
@@ -22,6 +22,7 @@ import org.eigenbase.rel.*;
import org.eigenbase.relopt.*;
import org.eigenbase.rex.*;
import org.eigenbase.util.Bug;
+import org.eigenbase.util.Holder;
import com.google.common.collect.ImmutableList;
@@ -31,39 +32,29 @@ import com.google.common.collect.ImmutableList;
*/
public abstract class PushFilterPastJoinRule extends RelOptRule {
public static final PushFilterPastJoinRule FILTER_ON_JOIN =
- new PushFilterPastJoinRule(
- operand(
- FilterRel.class,
- operand(JoinRelBase.class, any())),
- "PushFilterPastJoinRule:filter") {
- @Override
- public void onMatch(RelOptRuleCall call) {
- FilterRel filter = call.rel(0);
- JoinRelBase join = call.rel(1);
- perform(call, filter, join);
- }
- };
+ new PushFilterIntoJoinRule(true);
+
+ /** Dumber version of {@link #FILTER_ON_JOIN}. Not intended for production
+ * use, but keeps some tests working for which {@code FILTER_ON_JOIN} is too
+ * smart. */
+ public static final PushFilterPastJoinRule DUMB_FILTER_ON_JOIN =
+ new PushFilterIntoJoinRule(false);
public static final PushFilterPastJoinRule JOIN =
- new PushFilterPastJoinRule(
- operand(JoinRelBase.class, any()),
- "PushFilterPastJoinRule:no-filter") {
- @Override
- public void onMatch(RelOptRuleCall call) {
- JoinRelBase join = call.rel(0);
- perform(call, null, join);
- }
- };
+ new PushDownJoinConditionRule();
+
+ /** Whether to try to strengthen join-type. */
+ private final boolean smart;
//~ Constructors -----------------------------------------------------------
/**
* Creates a PushFilterPastJoinRule with an explicit root operand.
*/
- private PushFilterPastJoinRule(
- RelOptRuleOperand operand,
- String id) {
+ private PushFilterPastJoinRule(RelOptRuleOperand operand, String id,
+ boolean smart) {
super(operand, "PushFilterRule: " + id);
+ this.smart = smart;
}
//~ Methods ----------------------------------------------------------------
@@ -109,15 +100,18 @@ public abstract class PushFilterPastJoinRule extends RelOptRule {
// filters. They can be pushed down if they are not on the NULL
// generating side.
boolean filterPushed = false;
+ final Holder<JoinRelType> joinTypeHolder = Holder.of(join.getJoinType());
if (RelOptUtil.classifyFilters(
join,
aboveFilters,
- join.getJoinType() == JoinRelType.INNER,
+ join.getJoinType(),
!join.getJoinType().generatesNullsOnLeft(),
!join.getJoinType().generatesNullsOnRight(),
joinFilters,
leftFilters,
- rightFilters)) {
+ rightFilters,
+ joinTypeHolder,
+ smart)) {
filterPushed = true;
}
@@ -127,12 +121,14 @@ public abstract class PushFilterPastJoinRule extends RelOptRule {
if (RelOptUtil.classifyFilters(
join,
joinFilters,
- false,
+ null,
!join.getJoinType().generatesNullsOnRight(),
!join.getJoinType().generatesNullsOnLeft(),
joinFilters,
leftFilters,
- rightFilters)) {
+ rightFilters,
+ joinTypeHolder,
+ smart)) {
filterPushed = true;
}
@@ -142,7 +138,7 @@ public abstract class PushFilterPastJoinRule extends RelOptRule {
// create FilterRels on top of the children if any filters were
// pushed to them
- RexBuilder rexBuilder = join.getCluster().getRexBuilder();
+ final RexBuilder rexBuilder = join.getCluster().getRexBuilder();
RelNode leftRel =
createFilterOnRel(
rexBuilder,
@@ -161,7 +157,9 @@ public abstract class PushFilterPastJoinRule extends RelOptRule {
if (joinFilters.size() == 0) {
// if nothing actually got pushed and there is nothing leftover,
// then this rule is a no-op
- if ((leftFilters.size() == 0) && (rightFilters.size() == 0)) {
+ if (leftFilters.isEmpty()
+ && rightFilters.isEmpty()
+ && joinTypeHolder.get() == join.getJoinType()) {
return;
}
joinFilter = rexBuilder.makeLiteral(true);
@@ -175,7 +173,7 @@ public abstract class PushFilterPastJoinRule extends RelOptRule {
joinFilter,
leftRel,
rightRel,
- join.getJoinType(),
+ joinTypeHolder.get(),
join.isSemiJoinDone());
call.getPlanner().onCopy(join, newJoinRel);
@@ -190,6 +188,10 @@ public abstract class PushFilterPastJoinRule extends RelOptRule {
call.getPlanner().onCopy(filter, rightRel);
}
+ // Create a project on top of the join if some of the columns have become
+ // NOT NULL due to the join-type getting stricter.
+ newJoinRel = RelOptUtil.createCastRel(newJoinRel, join.getRowType(), false);
+
// create a FilterRel on top of the join if needed
RelNode newRel =
createFilterOnRel(rexBuilder, newJoinRel, aboveFilters);
@@ -217,6 +219,41 @@ public abstract class PushFilterPastJoinRule extends RelOptRule {
}
return CalcRel.createFilter(rel, andFilters);
}
+
+ /** Rule that pushes parts of the join condition to its inputs. */
+ private static class PushDownJoinConditionRule
+ extends PushFilterPastJoinRule {
+ public PushDownJoinConditionRule() {
+ super(RelOptRule.operand(JoinRelBase.class, RelOptRule.any()),
+ "PushFilterPastJoinRule:no-filter",
+ true);
+ }
+
+ @Override
+ public void onMatch(RelOptRuleCall call) {
+ JoinRelBase join = call.rel(0);
+ perform(call, null, join);
+ }
+ }
+
+ /** Rule that tries to push filter expressions into a join
+ * condition and into the inputs of the join. */
+ private static class PushFilterIntoJoinRule extends PushFilterPastJoinRule {
+ public PushFilterIntoJoinRule(boolean smart) {
+ super(
+ RelOptRule.operand(FilterRel.class,
+ RelOptRule.operand(JoinRelBase.class, RelOptRule.any())),
+ "PushFilterPastJoinRule:filter",
+ smart);
+ }
+
+ @Override
+ public void onMatch(RelOptRuleCall call) {
+ FilterRel filter = call.rel(0);
+ JoinRelBase join = call.rel(1);
+ perform(call, filter, join);
+ }
+ }
}
// End PushFilterPastJoinRule.java
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java b/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
index 9f00333..e96e425 100644
--- a/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
+++ b/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
@@ -35,6 +35,7 @@ import net.hydromatic.linq4j.Ord;
import net.hydromatic.optiq.util.BitSets;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
/**
* <code>RelOptUtil</code> defines static utility methods for use in optimizing
@@ -1845,26 +1846,29 @@ public abstract class RelOptUtil {
*
* @param joinRel join node
* @param filters filters to be classified
- * @param pushJoin true if filters originated from above the join node and
- * the join is an inner join
+ * @param joinType join type; determines whether filters can be pushed
+ * into the ON clause
* @param pushLeft true if filters can be pushed to the left
* @param pushRight true if filters can be pushed to the right
* @param joinFilters list of filters to push to the join
* @param leftFilters list of filters to push to the left child
* @param rightFilters list of filters to push to the right child
+ * @param smart Whether to try to strengthen the join type
* @return true if at least one filter was pushed
*/
public static boolean classifyFilters(
RelNode joinRel,
List<RexNode> filters,
- boolean pushJoin,
+ JoinRelType joinType,
boolean pushLeft,
boolean pushRight,
List<RexNode> joinFilters,
List<RexNode> leftFilters,
- List<RexNode> rightFilters) {
+ List<RexNode> rightFilters,
+ Holder<JoinRelType> joinTypeHolder,
+ boolean smart) {
RexBuilder rexBuilder = joinRel.getCluster().getRexBuilder();
- boolean filterPushed = false;
+ final JoinRelType oldJoinType = joinType;
List<RelDataTypeField> joinFields = joinRel.getRowType().getFieldList();
final int nTotalFields = joinFields.size();
final int nSysFields = 0; // joinRel.getSystemFieldList().size();
@@ -1882,11 +1886,9 @@ public abstract class RelOptUtil {
BitSet rightBitmap =
BitSets.range(nSysFields + nFieldsLeft, nTotalFields);
- ListIterator<RexNode> filterIter = filters.listIterator();
- while (filterIter.hasNext()) {
- RexNode filter = filterIter.next();
-
- final BitSet filterBitmap = InputFinder.bits(filter);
+ final List<RexNode> filtersToRemove = Lists.newArrayList();
+ for (RexNode filter : filters) {
+ final InputFinder inputFinder = InputFinder.analyze(filter);
// REVIEW - are there any expressions that need special handling
// and therefore cannot be pushed?
@@ -1894,9 +1896,7 @@ public abstract class RelOptUtil {
// filters can be pushed to the left child if the left child
// does not generate NULLs and the only columns referenced in
// the filter originate from the left child
- if (pushLeft && BitSets.contains(leftBitmap, filterBitmap)) {
- filterPushed = true;
-
+ if (pushLeft && BitSets.contains(leftBitmap, inputFinder.inputBitSet)) {
// ignore filters that always evaluate to true
if (!filter.isAlwaysTrue()) {
// adjust the field references in the filter to reflect
@@ -1915,15 +1915,13 @@ public abstract class RelOptUtil {
leftFilters.add(shiftedFilter);
}
- filterIter.remove();
+ filtersToRemove.add(filter);
// filters can be pushed to the right child if the right child
// does not generate NULLs and the only columns referenced in
// the filter originate from the right child
- } else if (
- pushRight
- && BitSets.contains(rightBitmap, filterBitmap)) {
- filterPushed = true;
+ } else if (pushRight
+ && BitSets.contains(rightBitmap, inputFinder.inputBitSet)) {
if (!filter.isAlwaysTrue()) {
// adjust the field references in the filter to reflect
// that fields in the right now shift over to the left;
@@ -1931,7 +1929,7 @@ public abstract class RelOptUtil {
// child, the types of the source should match the dest
// so we don't need to explicitly pass the destination
// fields to RexInputConverter
- final RexNode shilftedFilter =
+ final RexNode shiftedFilter =
shiftFilter(
nSysFields + nFieldsLeft,
nTotalFields,
@@ -1941,23 +1939,55 @@ public abstract class RelOptUtil {
nTotalFields,
rightFields,
filter);
- rightFilters.add(shilftedFilter);
+ rightFilters.add(shiftedFilter);
}
- filterIter.remove();
+ filtersToRemove.add(filter);
// if the filter can't be pushed to either child and the join
// is an inner join, push them to the join if they originated
// from above the join
- } else if (pushJoin) {
- filterPushed = true;
+ } else if (joinType == JoinRelType.INNER) {
joinFilters.add(filter);
- filterIter.remove();
+ filtersToRemove.add(filter);
+
+ // If the filter will only evaluate to true if fields from the left
+ // are not null, and the left is null-generating, then we can make the
+ // left. Similarly for the right.
+ } else {
+ if (smart
+ && joinType != null
+ && joinType.generatesNullsOnRight()
+ && inputFinder.strongBitSet.intersects(rightBitmap)) {
+ filtersToRemove.add(filter);
+ joinType = joinType.cancelNullsOnRight();
+ joinTypeHolder.set(joinType);
+ if (!joinFilters.contains(filter)) {
+ joinFilters.add(filter);
+ }
+ }
+ if (smart
+ && joinType != null
+ && joinType.generatesNullsOnLeft()
+ && inputFinder.strongBitSet.intersects(leftBitmap)) {
+ filtersToRemove.add(filter);
+ joinType = joinType.cancelNullsOnLeft();
+ joinTypeHolder.set(joinType);
+ if (!joinFilters.contains(filter)) {
+ joinFilters.add(filter);
+ }
+ }
}
// else, leave the filter where it is
}
- return filterPushed;
+ // Remove filters after the loop, to prevent concurrent modification.
+ if (!filtersToRemove.isEmpty()) {
+ filters.removeAll(filtersToRemove);
+ }
+
+ // Did anything change?
+ return !filtersToRemove.isEmpty() || joinType != oldJoinType;
}
private static RexNode shiftFilter(
@@ -2488,28 +2518,60 @@ public abstract class RelOptUtil {
* Visitor which builds a bitmap of the inputs used by an expression.
*/
public static class InputFinder extends RexVisitorImpl<Void> {
- private final BitSet rexRefSet;
+ final BitSet inputBitSet;
+ final BitSet strongBitSet = new BitSet();
private final Set<RelDataTypeField> extraFields;
- public InputFinder(BitSet rexRefSet) {
- this(rexRefSet, null);
+ public InputFinder(BitSet inputBitSet) {
+ this(inputBitSet, null);
}
- public InputFinder(
- BitSet rexRefSet,
- Set<RelDataTypeField> extraFields) {
+ public InputFinder(BitSet inputBitSet, Set<RelDataTypeField> extraFields) {
super(true);
- this.rexRefSet = rexRefSet;
+ this.inputBitSet = inputBitSet;
this.extraFields = extraFields;
}
+ /** Returns an input finder that has analyzed a given expression. */
+ public static InputFinder analyze(RexNode node) {
+ final InputFinder inputFinder = new InputFinder(new BitSet());
+ node.accept(inputFinder);
+ inputFinder.strong(node);
+ return inputFinder;
+ }
+
+ private byte strong(RexNode node) {
+ switch (node.getKind()) {
+ case IS_TRUE:
+ case IS_NOT_NULL:
+ case AND:
+ case EQUALS:
+ case NOT_EQUALS:
+ case LESS_THAN:
+ case LESS_THAN_OR_EQUAL:
+ case GREATER_THAN:
+ case GREATER_THAN_OR_EQUAL:
+ return strong(((RexCall) node).getOperands());
+ case INPUT_REF:
+ strongBitSet.set(((RexInputRef) node).getIndex());
+ return 0;
+ default:
+ return 0;
+ }
+ }
+
+ private byte strong(List<RexNode> operands) {
+ for (RexNode operand : operands) {
+ strong(operand);
+ }
+ return 0;
+ }
+
/**
* Returns a bit set describing the inputs used by an expression.
*/
public static BitSet bits(RexNode node) {
- final BitSet inputBitSet = new BitSet();
- node.accept(new InputFinder(inputBitSet));
- return inputBitSet;
+ return analyze(node).inputBitSet;
}
/**
@@ -2523,7 +2585,7 @@ public abstract class RelOptUtil {
}
public Void visitInputRef(RexInputRef inputRef) {
- rexRefSet.set(inputRef.getIndex());
+ inputBitSet.set(inputRef.getIndex());
return null;
}
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/main/java/org/eigenbase/rex/RexCall.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rex/RexCall.java b/core/src/main/java/org/eigenbase/rex/RexCall.java
index 3baf669..9d712b8 100644
--- a/core/src/main/java/org/eigenbase/rex/RexCall.java
+++ b/core/src/main/java/org/eigenbase/rex/RexCall.java
@@ -109,6 +109,27 @@ public class RexCall extends RexNode {
return type;
}
+ @Override
+ public boolean isAlwaysTrue() {
+ // "c IS NOT NULL" occurs when we expand EXISTS.
+ // This reduction allows us to convert it to a semi-join.
+ switch (getKind()) {
+ case IS_NOT_NULL:
+ return !operands.get(0).getType().isNullable();
+ default:
+ return false;
+ }
+ }
+
+ @Override public boolean isAlwaysFalse() {
+ switch (getKind()) {
+ case IS_NULL:
+ return !operands.get(0).getType().isNullable();
+ default:
+ return false;
+ }
+ }
+
public SqlKind getKind() {
return op.kind;
}
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/main/java/org/eigenbase/sql2rel/SqlToRelConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/sql2rel/SqlToRelConverter.java b/core/src/main/java/org/eigenbase/sql2rel/SqlToRelConverter.java
index c56c788..a03eb1b 100644
--- a/core/src/main/java/org/eigenbase/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/eigenbase/sql2rel/SqlToRelConverter.java
@@ -4084,7 +4084,7 @@ public class SqlToRelConverter {
if (needTruthTest) {
fieldAccess =
rexBuilder.makeCall(
- SqlStdOperatorTable.IS_TRUE,
+ SqlStdOperatorTable.IS_NOT_NULL,
fieldAccess);
}
return fieldAccess;
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/test/java/org/eigenbase/test/RelMetadataTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/eigenbase/test/RelMetadataTest.java b/core/src/test/java/org/eigenbase/test/RelMetadataTest.java
index 646ed09..a3484ae 100644
--- a/core/src/test/java/org/eigenbase/test/RelMetadataTest.java
+++ b/core/src/test/java/org/eigenbase/test/RelMetadataTest.java
@@ -439,10 +439,16 @@ public class RelMetadataTest extends SqlToRelTestBase {
@Test public void testSelectivityIsNotNullFilter() {
checkFilterSelectivity(
- "select * from emp where deptno is not null",
+ "select * from emp where mgr is not null",
DEFAULT_NOTNULL_SELECTIVITY);
}
+ @Test public void testSelectivityIsNotNullFilterOnNotNullColumn() {
+ checkFilterSelectivity(
+ "select * from emp where deptno is not null",
+ 1.0d);
+ }
+
@Test public void testSelectivityComparisonFilter() {
checkFilterSelectivity(
"select * from emp where deptno > 10",
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/test/java/org/eigenbase/test/RelOptRulesTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/eigenbase/test/RelOptRulesTest.java b/core/src/test/java/org/eigenbase/test/RelOptRulesTest.java
index 91347a0..5ebc1ea 100644
--- a/core/src/test/java/org/eigenbase/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/eigenbase/test/RelOptRulesTest.java
@@ -128,6 +128,27 @@ public class RelOptRulesTest extends RelOptTestBase {
"select 1 from emp inner join dept on emp.deptno = dept.deptno");
}
+ @Test public 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(MergeProjectRule.INSTANCE)
+ .addRuleInstance(PushFilterPastProjectRule.INSTANCE)
+ .build();
+ final HepProgram program =
+ HepProgram.builder()
+ .addRuleInstance(PushFilterPastJoinRule.FILTER_ON_JOIN)
+ .build();
+ checkPlanning(tester.withDecorrelation(true).withTrim(true), preProgram,
+ new HepPlanner(program),
+ "select * from dept where exists (\n"
+ + " select * from emp\n"
+ + " where emp.deptno = dept.deptno\n"
+ + " and emp.sal > 100)");
+ }
+
@Test public void testPushFilterThroughOuterJoin() {
checkPlanning(
PushFilterPastJoinRule.FILTER_ON_JOIN,
@@ -776,10 +797,12 @@ public class RelOptRulesTest extends RelOptTestBase {
final DiffRepository diffRepos = getDiffRepos();
String sql = diffRepos.expand(null, "${sql}");
- HepProgram program = new HepProgramBuilder().addRuleCollection(
- Lists.newArrayList(PushFilterPastJoinRule.FILTER_ON_JOIN,
- PushFilterPastJoinRule.JOIN, PushFilterPastProjectRule.INSTANCE,
- PushFilterPastSetOpRule.INSTANCE)).build();
+ HepProgram program = new HepProgramBuilder()
+ .addRuleInstance(PushFilterPastJoinRule.DUMB_FILTER_ON_JOIN)
+ .addRuleInstance(PushFilterPastJoinRule.JOIN)
+ .addRuleInstance(PushFilterPastProjectRule.INSTANCE)
+ .addRuleInstance(PushFilterPastSetOpRule.INSTANCE)
+ .build();
HepPlanner planner = new HepPlanner(program);
RelNode relInitial = tester.convertSqlToRel(sql);
@@ -803,12 +826,12 @@ public class RelOptRulesTest extends RelOptTestBase {
HepProgram program2 = new HepProgramBuilder()
.addMatchOrder(HepMatchOrder.BOTTOM_UP)
- .addRuleCollection(
- Lists.newArrayList(PushFilterPastJoinRule.FILTER_ON_JOIN,
- PushFilterPastJoinRule.JOIN,
- PushFilterPastProjectRule.INSTANCE,
- PushFilterPastSetOpRule.INSTANCE,
- TransitivePredicatesOnJoinRule.INSTANCE)).build();
+ .addRuleInstance(PushFilterPastJoinRule.DUMB_FILTER_ON_JOIN)
+ .addRuleInstance(PushFilterPastJoinRule.JOIN)
+ .addRuleInstance(PushFilterPastProjectRule.INSTANCE)
+ .addRuleInstance(PushFilterPastSetOpRule.INSTANCE)
+ .addRuleInstance(TransitivePredicatesOnJoinRule.INSTANCE)
+ .build();
HepPlanner planner2 = new HepPlanner(program2);
planner.registerMetadataProviders(list);
planner2.setRoot(relAfter);
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/test/java/org/eigenbase/test/SqlToRelTestBase.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/eigenbase/test/SqlToRelTestBase.java b/core/src/test/java/org/eigenbase/test/SqlToRelTestBase.java
index d8b78a8..5c761dc 100644
--- a/core/src/test/java/org/eigenbase/test/SqlToRelTestBase.java
+++ b/core/src/test/java/org/eigenbase/test/SqlToRelTestBase.java
@@ -66,7 +66,7 @@ public abstract class SqlToRelTestBase {
}
protected Tester createTester() {
- return new TesterImpl(getDiffRepos(), true, null);
+ return new TesterImpl(getDiffRepos(), true, false, null);
}
/**
@@ -172,6 +172,9 @@ public abstract class SqlToRelTestBase {
Tester withCatalogReaderFactory(
Function<RelDataTypeFactory, Prepare.CatalogReader> factory);
+
+ /** Returns a tester that optionally trims unused fields. */
+ Tester withTrim(boolean enable);
}
//~ Inner Classes ----------------------------------------------------------
@@ -394,6 +397,7 @@ public abstract class SqlToRelTestBase {
private SqlOperatorTable opTab;
private final DiffRepository diffRepos;
private final boolean enableDecorrelate;
+ private final boolean enableTrim;
private final Function<RelDataTypeFactory, Prepare.CatalogReader>
catalogReaderFactory;
private RelDataTypeFactory typeFactory;
@@ -403,13 +407,16 @@ public abstract class SqlToRelTestBase {
*
* @param diffRepos Diff repository
* @param enableDecorrelate Whether to decorrelate
+ * @param enableTrim Whether to trim unused fields
* @param catalogReaderFactory Function to create catalog reader, or null
*/
protected TesterImpl(DiffRepository diffRepos, boolean enableDecorrelate,
+ boolean enableTrim,
Function<RelDataTypeFactory, Prepare.CatalogReader>
catalogReaderFactory) {
this.diffRepos = diffRepos;
this.enableDecorrelate = enableDecorrelate;
+ this.enableTrim = enableTrim;
this.catalogReaderFactory = catalogReaderFactory;
}
@@ -437,10 +444,16 @@ public abstract class SqlToRelTestBase {
RelNode rel =
converter.convertQuery(validatedQuery, false, true);
assert rel != null;
- if (enableDecorrelate) {
+ if (enableDecorrelate || enableTrim) {
rel = converter.flattenTypes(rel, true);
+ }
+ if (enableDecorrelate) {
rel = converter.decorrelate(sqlQuery, rel);
}
+ if (enableTrim) {
+ converter.setTrimUnusedFields(true);
+ rel = converter.trimUnusedFields(rel);
+ }
return rel;
}
@@ -591,12 +604,18 @@ public abstract class SqlToRelTestBase {
public TesterImpl withDecorrelation(boolean enable) {
return this.enableDecorrelate == enable ? this
- : new TesterImpl(diffRepos, enable, catalogReaderFactory);
+ : new TesterImpl(diffRepos, enable, enableTrim, catalogReaderFactory);
+ }
+
+ public Tester withTrim(boolean enable) {
+ return this.enableTrim == enable ? this
+ : new TesterImpl(diffRepos, enableDecorrelate, enable,
+ catalogReaderFactory);
}
public Tester withCatalogReaderFactory(
Function<RelDataTypeFactory, Prepare.CatalogReader> factory) {
- return new TesterImpl(diffRepos, enableDecorrelate, factory);
+ return new TesterImpl(diffRepos, enableDecorrelate, false, factory);
}
}
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/test/resources/org/eigenbase/test/RelOptRulesTest.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/eigenbase/test/RelOptRulesTest.xml b/core/src/test/resources/org/eigenbase/test/RelOptRulesTest.xml
index e33ca4a..bee6e64 100644
--- a/core/src/test/resources/org/eigenbase/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/eigenbase/test/RelOptRulesTest.xml
@@ -2003,4 +2003,50 @@ ProjectRel(EXPR$0=[1])
]]>
</Resource>
</TestCase>
+ <TestCase name="testStrengthenJoinType">
+ <Resource name="sql">
+ <![CDATA[select * from dept where exists (
+ select * from emp
+ where emp.deptno = dept.deptno
+ and emp.sal > 100)]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+ProjectRel(DEPTNO=[$0], NAME=[$1])
+ ProjectRel(DEPTNO=[$0], NAME=[$1], $f0=[$3])
+ FilterRel(condition=[IS NOT NULL($3)])
+ JoinRel(condition=[=($0, $2)], joinType=[left])
+ TableAccessRel(table=[[CATALOG, SALES, DEPT]])
+ AggregateRel(group=[{0}], agg#0=[MIN($1)])
+ ProjectRel($f01=[$2], $f0=[true])
+ FilterRel(condition=[AND(=($1, $2), >($0, 100))])
+ JoinRel(condition=[true], joinType=[inner])
+ ProjectRel(SAL=[$5], DEPTNO=[$7])
+ TableAccessRel(table=[[CATALOG, SALES, EMP]])
+ AggregateRel(group=[{0}])
+ ProjectRel($f0=[$0])
+ ProjectRel(DEPTNO=[$0])
+ TableAccessRel(table=[[CATALOG, SALES, DEPT]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+ProjectRel(DEPTNO=[$0], NAME=[$1])
+ ProjectRel(DEPTNO=[$0], NAME=[$1], $f0=[$3])
+ ProjectRel(DEPTNO=[$0], NAME=[$1], $f01=[CAST($2):INTEGER], $f1=[CAST($3):BOOLEAN])
+ JoinRel(condition=[=($0, $2)], joinType=[inner])
+ TableAccessRel(table=[[CATALOG, SALES, DEPT]])
+ AggregateRel(group=[{0}], agg#0=[MIN($1)])
+ ProjectRel($f01=[$2], $f0=[true])
+ JoinRel(condition=[=($1, $2)], joinType=[inner])
+ FilterRel(condition=[>($0, 100)])
+ ProjectRel(SAL=[$5], DEPTNO=[$7])
+ TableAccessRel(table=[[CATALOG, SALES, EMP]])
+ AggregateRel(group=[{0}])
+ ProjectRel($f0=[$0])
+ ProjectRel(DEPTNO=[$0])
+ TableAccessRel(table=[[CATALOG, SALES, DEPT]])
+]]>
+ </Resource>
+ </TestCase>
</Root>
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/test/resources/org/eigenbase/test/SqlToRelConverterTest.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/eigenbase/test/SqlToRelConverterTest.xml b/core/src/test/resources/org/eigenbase/test/SqlToRelConverterTest.xml
index ae448bc..1e4d24b 100644
--- a/core/src/test/resources/org/eigenbase/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/eigenbase/test/SqlToRelConverterTest.xml
@@ -348,7 +348,7 @@ ProjectRel(DEPTNO=[$0], NAME=[$1], EMPSET=[$2])
<Resource name="plan">
<![CDATA[
ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
- FilterRel(condition=[IS TRUE($9)])
+ FilterRel(condition=[IS NOT NULL($9)])
JoinRel(condition=[true], joinType=[left])
TableAccessRel(table=[[CATALOG, SALES, EMP]])
AggregateRel(group=[{}], agg#0=[MIN($0)])
@@ -378,7 +378,7 @@ ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5],
<Resource name="plan">
<![CDATA[
ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
- FilterRel(condition=[IS TRUE($9)])
+ FilterRel(condition=[IS NOT NULL($9)])
CorrelatorRel(condition=[true], joinType=[left], correlations=[[var0=offset7]])
TableAccessRel(table=[[CATALOG, SALES, EMP]])
AggregateRel(group=[{}], agg#0=[MIN($0)])
@@ -1594,7 +1594,7 @@ where exists (
<Resource name="plan">
<![CDATA[
ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
- FilterRel(condition=[IS TRUE($9)])
+ FilterRel(condition=[IS NOT NULL($9)])
CorrelatorRel(condition=[true], joinType=[left], correlations=[[var1=offset7, var0=offset7]])
TableAccessRel(table=[[CATALOG, SALES, EMP]])
AggregateRel(group=[{}], agg#0=[MIN($0)])
@@ -1698,7 +1698,7 @@ where exists (
<Resource name="plan">
<![CDATA[
ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
- FilterRel(condition=[IS TRUE($9)])
+ FilterRel(condition=[IS NOT NULL($9)])
ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], $f0=[$11])
JoinRel(condition=[AND(=($7, $10), =($7, $9))], joinType=[left])
TableAccessRel(table=[[CATALOG, SALES, EMP]])
@@ -1728,7 +1728,7 @@ ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5],
<Resource name="plan">
<![CDATA[
ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
- FilterRel(condition=[IS TRUE($9)])
+ FilterRel(condition=[IS NOT NULL($9)])
ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], $f0=[$10])
JoinRel(condition=[=($7, $9)], joinType=[left])
TableAccessRel(table=[[CATALOG, SALES, EMP]])
@@ -1753,7 +1753,7 @@ ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5],
<Resource name="plan">
<![CDATA[
ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
- FilterRel(condition=[IS TRUE($9)])
+ FilterRel(condition=[IS NOT NULL($9)])
CorrelatorRel(condition=[true], joinType=[left], correlations=[[var0=offset7]])
TableAccessRel(table=[[CATALOG, SALES, EMP]])
AggregateRel(group=[{}], agg#0=[MIN($0)])
@@ -1773,7 +1773,7 @@ ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5],
<Resource name="plan">
<![CDATA[
ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
- FilterRel(condition=[IS TRUE($9)])
+ FilterRel(condition=[IS NOT NULL($9)])
ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], $f0=[$9])
CorrelatorRel(condition=[true], joinType=[left], correlations=[[var0=offset7]])
TableAccessRel(table=[[CATALOG, SALES, EMP]])