This is an automated email from the ASF dual-hosted git repository.

mbudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new aa2af60e58 [CALCITE-6914] Expand join-dependent predicates from 
disjunction
aa2af60e58 is described below

commit aa2af60e581ba9f6d1007d10cc883a673b5e261a
Author: Silun Dong <[email protected]>
AuthorDate: Mon Mar 31 16:37:00 2025 +0800

    [CALCITE-6914] Expand join-dependent predicates from disjunction
---
 .../org/apache/calcite/rel/rules/CoreRules.java    |  12 +
 .../rel/rules/ExpandDisjunctionForTableRule.java   | 430 +++++++++++++++++++++
 .../org/apache/calcite/test/RelOptRulesTest.java   |  36 ++
 .../org/apache/calcite/test/RelOptRulesTest.xml    |  46 +++
 4 files changed, 524 insertions(+)

diff --git a/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java 
b/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
index c05f18bbaf..bd2ebb06b5 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
@@ -864,4 +864,16 @@ private CoreRules() {}
   @Experimental
   public static final DphypJoinReorderRule HYPER_GRAPH_OPTIMIZE =
       DphypJoinReorderRule.Config.DEFAULT.toRule();
+
+  /** Rule that expands disjunction in the condition of a {@link Filter}.
+   *
+   * @see #EXPAND_JOIN_DISJUNCTION_GLOBAL */
+  public static final ExpandDisjunctionForTableRule 
EXPAND_FILTER_DISJUNCTION_GLOBAL =
+      ExpandDisjunctionForTableRule.Config.FILTER.toRule();
+
+  /** Rule that expands disjunction in the condition of a {@link Join}.
+   *
+   * @see #EXPAND_FILTER_DISJUNCTION_GLOBAL */
+  public static final ExpandDisjunctionForTableRule 
EXPAND_JOIN_DISJUNCTION_GLOBAL =
+      ExpandDisjunctionForTableRule.Config.JOIN.toRule();
 }
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ExpandDisjunctionForTableRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/ExpandDisjunctionForTableRule.java
new file mode 100644
index 0000000000..a601f5ed8a
--- /dev/null
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/ExpandDisjunctionForTableRule.java
@@ -0,0 +1,430 @@
+/*
+ * 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.rel.rules;
+
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Filter;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexTableInputRef;
+import org.apache.calcite.rex.RexUtil;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.util.ControlFlowException;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import com.google.common.collect.Lists;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.immutables.value.Value;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Rule to expand disjunction for single table in condition of a {@link 
Filter} or {@link Join},
+ * It makes sense to make this optimization part of the predicate pushdown. 
For example:
+ *
+ * <blockquote><pre>{@code
+ * select t1.name from t1, t2
+ * where t1.id = t2.id
+ * and (
+ *  (t1.id > 20 and t2.height < 50)
+ *  or
+ *  (t1.weight < 200 and t2.sales > 100)
+ * )
+ * }</pre></blockquote>
+ *
+ * <p>we can expand to obtain the condition
+ *
+ * <blockquote><pre>{@code
+ * select t1.name from t1, t2
+ * where t1.id = t2.id
+ * and (
+ *  (t1.id > 20 and t2.height < 50)
+ *  or
+ *  (t1.weight < 200 and t2.sales > 100)
+ * )
+ * and (t1.id > 20 or t1.weight < 200)
+ * and (t2.height < 50 or t2.sales > 100)
+ * }</pre></blockquote>
+ *
+ * <p>new generated predicates are redundant, but they could be pushed down to
+ * scan operator of t1/t2 and reduce the cardinality.
+ *
+ * <p>Note: This rule should only be applied once and it should be used before
+ * {@link CoreRules#FILTER_INTO_JOIN} and {@link 
CoreRules#JOIN_CONDITION_PUSH}.
+ * In order to avoid it interacting with other expression-related rules and 
causing
+ * an infinite loop, it is recommended to use it alone in HepPlanner first,
+ * and then do predicate pushdown.
+ *
+ * @see CoreRules#EXPAND_FILTER_DISJUNCTION_GLOBAL
+ * @see CoreRules#EXPAND_JOIN_DISJUNCTION_GLOBAL
+ */
[email protected]
+public class ExpandDisjunctionForTableRule
+    extends RelRule<ExpandDisjunctionForTableRule.Config>
+    implements TransformationRule {
+
+  /**
+   * Creates a ExpandDisjunctionForTableRule.
+   */
+  protected ExpandDisjunctionForTableRule(Config config) {
+    super(config);
+  }
+
+  @Override public void onMatch(RelOptRuleCall call) {
+    config.matchHandler().accept(this, call);
+  }
+
+  /**
+   * Expand predicates for condition of a {@link Filter} or {@link Join}.
+   *
+   * @param condition   Condition to be expanded in Filter or Join
+   * @param relNode     The Filter or Join node
+   * @param fieldList   The field list of the Filter or Join inputs.
+   * @param relBuilder  Builder
+   * @param mq          RelMetadataQuery, which is used to get the expression 
lineage
+   *
+   * @return The redundant predicate expanded from the condition parameter. It 
is formed by
+   * concatenating the predicates that can be pushed down to single table with 
AND.
+   */
+  private RexNode apply(
+      RexNode condition,
+      RelNode relNode,
+      List<RelDataTypeField> fieldList,
+      RelBuilder relBuilder,
+      RelMetadataQuery mq) {
+    final Map<Integer, RexTableInputRef.RelTableRef> inputRefToTableRef = new 
HashMap<>();
+
+    ImmutableBitSet columnBits = RelOptUtil.InputFinder.bits(condition);
+    if (columnBits.isEmpty()) {
+      return condition;
+    }
+
+    // For each table discover the conditions that refer to columns of that
+    // table (and refer to no other tables)
+    for (int columnBit : columnBits.asList()) {
+      Set<RexNode> exprLineage =
+          mq.getExpressionLineage(
+              relNode,
+              RexInputRef.of(columnBit, fieldList));
+      // If mq.getExpressionLineage cannot get result, skip it
+      if (exprLineage == null) {
+        continue;
+      }
+      Set<RexTableInputRef.RelTableRef> relTableRefs =
+          RexUtil.gatherTableReferences(Lists.newArrayList(exprLineage));
+      // If the column is computed by an expression that depends on multiple 
tables, skip it
+      if (relTableRefs.size() != 1) {
+        continue;
+      }
+      inputRefToTableRef.put(columnBit, relTableRefs.iterator().next());
+    }
+
+    ExpandDisjunctionForTableHelper expandHelper =
+        new ExpandDisjunctionForTableHelper(inputRefToTableRef, relBuilder, 
config.bloat());
+    Map<RexTableInputRef.RelTableRef, RexNode> expandResult = 
expandHelper.expand(condition);
+
+    RexNode newCondition = condition;
+    for (RexNode expandCondition : expandResult.values()) {
+      newCondition = relBuilder.and(newCondition, expandCondition);
+    }
+    return newCondition;
+  }
+
+  private static void matchFilter(ExpandDisjunctionForTableRule rule, 
RelOptRuleCall call) {
+    Filter filter = call.rel(0);
+    RelMetadataQuery mq = call.getMetadataQuery();
+    RelBuilder relBuilder = call.builder();
+
+    RexNode newCondition =
+        rule.apply(
+            filter.getCondition(),
+            filter,
+            filter.getRowType().getFieldList(),
+            relBuilder,
+            mq);
+    if (newCondition.equals(filter.getCondition())) {
+      return;
+    }
+    Filter newFilter = filter.copy(filter.getTraitSet(), filter.getInput(), 
newCondition);
+    call.transformTo(newFilter);
+  }
+
+  private static void matchJoin(ExpandDisjunctionForTableRule rule, 
RelOptRuleCall call) {
+    Join join = call.rel(0);
+    RelMetadataQuery mq = call.getMetadataQuery();
+    RelBuilder relBuilder = call.builder();
+
+    List<RelDataTypeField> fieldList =
+        Lists.newArrayList(join.getLeft().getRowType().getFieldList());
+    fieldList.addAll(join.getRight().getRowType().getFieldList());
+    RexNode newCondition =
+        rule.apply(
+            join.getCondition(),
+            join,
+            fieldList,
+            relBuilder,
+            mq);
+    if (newCondition.equals(join.getCondition())) {
+      return;
+    }
+    Join newJoin =
+        join.copy(
+            join.getTraitSet(),
+            newCondition,
+            join.getLeft(),
+            join.getRight(),
+            join.getJoinType(),
+            join.isSemiJoinDone());
+    call.transformTo(newJoin);
+  }
+
+  /**
+   * Helper class to expand predicates.
+   */
+  private static class ExpandDisjunctionForTableHelper {
+    private final Map<Integer, RexTableInputRef.RelTableRef> 
inputRefToTableRef;
+
+    private final RelBuilder relBuilder;
+
+    private final int maxNodeCount;
+
+    // Used to record the number of redundant expressions expanded.
+    private int currentCount;
+
+    private ExpandDisjunctionForTableHelper(
+        Map<Integer, RexTableInputRef.RelTableRef> inputRefToTableRef,
+        RelBuilder relBuilder,
+        int maxNodeCount) {
+      this.inputRefToTableRef = inputRefToTableRef;
+      this.relBuilder = relBuilder;
+      this.maxNodeCount = maxNodeCount;
+    }
+
+    private Map<RexTableInputRef.RelTableRef, RexNode> expand(RexNode 
condition) {
+      try {
+        this.currentCount = 0;
+        return expandDeep(condition);
+      } catch (OverLimitException e) {
+        return new HashMap<>();
+      }
+    }
+
+    /**
+     * Expand predicates recursively that can be pushed down to single table.
+     *
+     * @param condition   Predicate to be expanded
+     * @return  A map from a table to a (combined) predicate that can be 
pushed down
+     * and only depends on columns of the table
+     */
+    private Map<RexTableInputRef.RelTableRef, RexNode> expandDeep(RexNode 
condition) {
+      Map<RexTableInputRef.RelTableRef, RexNode> additionalConditions = new 
HashMap<>();
+
+      ImmutableBitSet inputRefs = RelOptUtil.InputFinder.bits(condition);
+      if (inputRefs.isEmpty()) {
+        return additionalConditions;
+      }
+
+      RexTableInputRef.RelTableRef tableRef = 
inputRefsBelongToOneTable(inputRefs);
+      if (tableRef != null) {
+        // The condition already belongs to one table, return it directly
+        checkExpandCount(condition.nodeCount());
+        additionalConditions.put(tableRef, condition);
+        return additionalConditions;
+      }
+
+      // Recursively expand the expression according to whether it is a 
conjunction
+      // or a disjunction. If it is neither a disjunction nor a conjunction, 
it cannot
+      // be expanded further and an empty Map is returned.
+      switch (condition.getKind()) {
+      case AND:
+        List<RexNode> andOperands = RexUtil.flattenAnd(((RexCall) 
condition).getOperands());
+        for (RexNode andOperand : andOperands) {
+          Map<RexTableInputRef.RelTableRef, RexNode> operandResult = 
expandDeep(andOperand);
+          combinePredicatesUsingAnd(additionalConditions, operandResult);
+        }
+        break;
+      case OR:
+        List<RexNode> orOperands = RexUtil.flattenOr(((RexCall) 
condition).getOperands());
+        additionalConditions.putAll(expandDeep(orOperands.get(0)));
+        for (int i = 1; i < orOperands.size(); i++) {
+          Map<RexTableInputRef.RelTableRef, RexNode> operandResult =
+              expandDeep(orOperands.get(i));
+          combinePredicatesUsingOr(additionalConditions, operandResult);
+
+          if (additionalConditions.isEmpty()) {
+            break;
+          }
+        }
+        break;
+      default:
+        break;
+      }
+
+      return additionalConditions;
+    }
+
+    private RexTableInputRef.@Nullable RelTableRef inputRefsBelongToOneTable(
+        ImmutableBitSet inputRefs) {
+      RexTableInputRef.RelTableRef tableRef = 
inputRefToTableRef.get(inputRefs.nth(0));
+      if (tableRef == null) {
+        return null;
+      }
+      for (int inputBit : inputRefs.asList()) {
+        RexTableInputRef.RelTableRef inputBitTableRef = 
inputRefToTableRef.get(inputBit);
+        if (!tableRef.equals(inputBitTableRef)) {
+          return null;
+        }
+      }
+      return tableRef;
+    }
+
+    /**
+     * Combine predicates that depend on the same table using conjunctions.
+     * The result is returned by modifying baseMap. For example:
+     *
+     * <p>baseMap: {t1: p1, t2: p2}
+     *
+     * <p>forMergeMap: {t1: p11, t3: p3}
+     *
+     * <p>result: {t1: p1 AND p11, t2: p2, t3: p3}
+     *
+     * @param baseMap       Additional predicates that current conjunction has 
already saved
+     * @param forMergeMap   Additional predicates that current operand has 
expanded
+     */
+    private void combinePredicatesUsingAnd(
+        Map<RexTableInputRef.RelTableRef, RexNode> baseMap,
+        Map<RexTableInputRef.RelTableRef, RexNode> forMergeMap) {
+      for (Map.Entry<RexTableInputRef.RelTableRef, RexNode> entry : 
forMergeMap.entrySet()) {
+        RexNode mergedRex =
+            relBuilder.and(
+                entry.getValue(),
+                baseMap.getOrDefault(entry.getKey(), 
relBuilder.literal(true)));
+        int oriCount = entry.getValue().nodeCount()
+            + (baseMap.containsKey(entry.getKey())
+                ? baseMap.get(entry.getKey()).nodeCount()
+                : 0);
+        checkExpandCount(mergedRex.nodeCount() - oriCount);
+        baseMap.put(entry.getKey(), mergedRex);
+      }
+    }
+
+    /**
+     * Combine predicates that depend on the same table using disjunctions.
+     * The result is returned by modifying baseMap. For example:
+     *
+     * <p>baseMap: {t1: p1, t2: p2}
+     *
+     * <p>forMergeMap: {t1: p11, t3: p3}
+     *
+     * <p>result: {t1: p1 OR p11}
+     *
+     * @param baseMap       Additional predicates that current disjunction has 
already saved
+     * @param forMergeMap   Additional predicates that current operand has 
expanded
+     */
+    private void combinePredicatesUsingOr(
+        Map<RexTableInputRef.RelTableRef, RexNode> baseMap,
+        Map<RexTableInputRef.RelTableRef, RexNode> forMergeMap) {
+      if (baseMap.isEmpty()) {
+        return;
+      }
+
+      Iterator<Map.Entry<RexTableInputRef.RelTableRef, RexNode>> iterator =
+          baseMap.entrySet().iterator();
+      while (iterator.hasNext()) {
+        int forMergeNodeCount = 0;
+
+        Map.Entry<RexTableInputRef.RelTableRef, RexNode> entry = 
iterator.next();
+        if (!forMergeMap.containsKey(entry.getKey())) {
+          checkExpandCount(-entry.getValue().nodeCount());
+          iterator.remove();
+          continue;
+        } else {
+          forMergeNodeCount = forMergeMap.get(entry.getKey()).nodeCount();
+        }
+        RexNode mergedRex =
+            relBuilder.or(
+                entry.getValue(),
+                forMergeMap.get(entry.getKey()));
+        int oriCount = entry.getValue().nodeCount() + forMergeNodeCount;
+        checkExpandCount(mergedRex.nodeCount() - oriCount);
+        baseMap.put(entry.getKey(), mergedRex);
+      }
+    }
+
+    /**
+     * Check whether the number of redundant expressions generated in the 
expansion
+     * exceeds the limit.
+     */
+    private void checkExpandCount(int changeCount) {
+      currentCount += changeCount;
+      if (maxNodeCount > 0 && currentCount > maxNodeCount) {
+        throw OverLimitException.INSTANCE;
+      }
+    }
+
+    /** Exception to catch when we pass the limit. */
+    private static class OverLimitException extends ControlFlowException {
+      protected static final OverLimitException INSTANCE = new 
OverLimitException();
+
+      private OverLimitException() {}
+    }
+  }
+
+  /** Rule configuration. */
+  @Value.Immutable(singleton = false)
+  public interface Config extends RelRule.Config {
+    Config FILTER = ImmutableExpandDisjunctionForTableRule.Config.builder()
+        .withMatchHandler(ExpandDisjunctionForTableRule::matchFilter)
+        .build()
+        .withOperandSupplier(b -> b.operand(Filter.class).anyInputs());
+
+    Config JOIN = ImmutableExpandDisjunctionForTableRule.Config.builder()
+        .withMatchHandler(ExpandDisjunctionForTableRule::matchJoin)
+        .build()
+        .withOperandSupplier(b -> b.operand(Join.class).anyInputs());
+
+    @Override default ExpandDisjunctionForTableRule toRule() {
+      return new ExpandDisjunctionForTableRule(this);
+    }
+
+    /**
+     * Limit to the size growth of the condition allowed during expansion.
+     * Default is {@link RelOptUtil#DEFAULT_BLOAT} (100).
+     */
+    default int bloat() {
+      return RelOptUtil.DEFAULT_BLOAT;
+    }
+
+    /** Forwards a call to {@link #onMatch(RelOptRuleCall)}. */
+    MatchHandler<ExpandDisjunctionForTableRule> matchHandler();
+
+    /** Sets {@link #matchHandler()}. */
+    Config withMatchHandler(MatchHandler<ExpandDisjunctionForTableRule> 
matchHandler);
+  }
+}
diff --git a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java 
b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
index 9ccd441bad..65720a062c 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -10274,4 +10274,40 @@ private void 
checkLoptOptimizeJoinRule(LoptOptimizeJoinRule rule) {
       p.addRule(EnumerableRules.ENUMERABLE_PROJECT_RULE);
     }).check();
   }
+
+  @Test void testExpandFilterDisjunctionForTable() {
+    HepProgram program = new HepProgramBuilder()
+        .addRuleInstance(CoreRules.EXPAND_FILTER_DISJUNCTION_GLOBAL)
+        .build();
+
+    sql("select e.empno from emp as e, empnullables as en "
+        + "where e.empno = en.empno "
+        + "and "
+        + "( "
+        + "  (e.mgr > 100 and en.sal < 200) "
+        + "  or "
+        + "  e.comm < 50 "
+        + ") ")
+        .withPre(program)
+        .withRule(CoreRules.FILTER_INTO_JOIN)
+        .check();
+  }
+
+  @Test void testExpandJoinDisjunctionForTable() {
+    HepProgram program = new HepProgramBuilder()
+        .addRuleInstance(CoreRules.EXPAND_JOIN_DISJUNCTION_GLOBAL)
+        .build();
+
+    sql("select e.empno from emp as e inner join empnullables as en "
+        + "on e.empno = en.empno "
+        + "and "
+        + "( "
+        + "  (e.mgr > 100 and en.sal < 200) "
+        + "  or "
+        + "  (e.comm < 50 and en.deptno > 10) "
+        + ") ")
+        .withPre(program)
+        .withRule(CoreRules.JOIN_CONDITION_PUSH)
+        .check();
+  }
 }
diff --git 
a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml 
b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
index 28446b5b46..c3f66840f5 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -4233,6 +4233,29 @@ LogicalProject(EMPNO=[$0])
               LogicalProject(DEPTNO=[$0])
                 LogicalFilter(condition=[=($1, 'dept2')])
                   LogicalTableScan(table=[[CATALOG, SALES, DEPTNULLABLES]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testExpandFilterDisjunctionForTable">
+    <Resource name="sql">
+      <![CDATA[select e.empno from emp as e, empnullables as en where e.empno 
= en.empno and (   (e.mgr > 100 and en.sal < 200)   or   e.comm < 50 ) ]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(EMPNO=[$0])
+  LogicalFilter(condition=[AND(=($0, $9), OR(AND(>($3, 100), <($14, 200)), 
<($6, 50)), OR(>($3, 100), <($6, 50)))])
+    LogicalJoin(condition=[true], joinType=[inner])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+      LogicalTableScan(table=[[CATALOG, SALES, EMPNULLABLES]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(EMPNO=[$0])
+  LogicalJoin(condition=[AND(=($0, $9), OR(AND(>($3, 100), <($14, 200)), <($6, 
50)))], joinType=[inner])
+    LogicalFilter(condition=[OR(>($3, 100), <($6, 50))])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+    LogicalTableScan(table=[[CATALOG, SALES, EMPNULLABLES]])
 ]]>
     </Resource>
   </TestCase>
@@ -4625,6 +4648,29 @@ LogicalProject(EMPNO=[$0])
           LogicalProject(DEPTNO=[$7])
             LogicalFilter(condition=[>($0, 100)])
               LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testExpandJoinDisjunctionForTable">
+    <Resource name="sql">
+      <![CDATA[select e.empno from emp as e inner join empnullables as en on 
e.empno = en.empno and (   (e.mgr > 100 and en.sal < 200)   or   (e.comm < 50 
and en.deptno > 10) ) ]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(EMPNO=[$0])
+  LogicalJoin(condition=[AND(=($0, $9), OR(AND(>($3, 100), <($14, 200)), 
AND(<($6, 50), >($16, 10))), OR(<($14, 200), >($16, 10)), OR(>($3, 100), <($6, 
50)))], joinType=[inner])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+    LogicalTableScan(table=[[CATALOG, SALES, EMPNULLABLES]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(EMPNO=[$0])
+  LogicalJoin(condition=[AND(=($0, $9), OR(AND(>($3, 100), <($14, 200)), 
AND(<($6, 50), >($16, 10))))], joinType=[inner])
+    LogicalFilter(condition=[OR(>($3, 100), <($6, 50))])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+    LogicalFilter(condition=[OR(<($5, 200), >($7, 10))])
+      LogicalTableScan(table=[[CATALOG, SALES, EMPNULLABLES]])
 ]]>
     </Resource>
   </TestCase>

Reply via email to