hsyuan commented on a change in pull request #1451: [CALCITE-3334] Refinement 
for Substitution-Based MV Matching
URL: https://github.com/apache/calcite/pull/1451#discussion_r338828919
 
 

 ##########
 File path: core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java
 ##########
 @@ -1252,11 +1536,221 @@ public UnifyResult apply(UnifyRuleCall call) {
       if (!target.groupSet.contains(query.groupSet)) {
         return null;
       }
-      MutableRel result = unifyAggregates(query, target);
+      MutableRel result = unifyAggregates(query, null, target);
       if (result == null) {
         return null;
       }
-      return call.result(result);
+      return tryMergeParentCalcAndGenResult(call, result);
+    }
+  }
+
+  /**
+   * {@link SubstitutionVisitor.UnifyRule} that matches a
+   * {@link MutableUnion} to a {@link MutableUnion} where the query and target
+   * have the same inputs but might not have the same order.
+   */
+  private static class UnionToUnionUnifyRule extends AbstractUnifyRule {
+    public static final UnionToUnionUnifyRule INSTANCE = new 
UnionToUnionUnifyRule();
+
+    private UnionToUnionUnifyRule() {
+      super(any(MutableUnion.class), any(MutableUnion.class), 0);
+    }
+
+    public UnifyResult apply(UnifyRuleCall call) {
+      final MutableUnion query = (MutableUnion) call.query;
+      final MutableUnion target = (MutableUnion) call.target;
+      final List<MutableRel> queryInputs = new ArrayList<>(query.getInputs());
+      final List<MutableRel> targetInputs = new 
ArrayList<>(target.getInputs());
+      if (query.isAll() == target.isAll()
+          && sameRelCollectionNoOrderConsidered(queryInputs, targetInputs)) {
+        return call.result(target);
+      }
+      return null;
+    }
+  }
+
+  /**
+   * A {@link SubstitutionVisitor.UnifyRule} that matches a {@link 
MutableUnion}
+   * which has {@link MutableCalc} as child to a {@link MutableUnion}.
+   * We try to pull up the {@link MutableCalc} to top of {@link MutableUnion},
+   * then match the {@link MutableUnion} in query to {@link MutableUnion} in 
target.
+   */
+  private static class UnionOnCalcsToUnionUnifyRule extends AbstractUnifyRule {
+    public static final UnionOnCalcsToUnionUnifyRule INSTANCE =
+        new UnionOnCalcsToUnionUnifyRule();
+
+    private UnionOnCalcsToUnionUnifyRule() {
+      super(any(MutableUnion.class), any(MutableUnion.class), 0);
+    }
+
+    public UnifyResult apply(UnifyRuleCall call) {
+      final MutableUnion query = (MutableUnion) call.query;
+      final MutableUnion target = (MutableUnion) call.target;
+      final List<MutableCalc> queryInputs = new ArrayList<>();
+      final List<MutableRel> queryGrandInputs = new ArrayList<>();
+      List<MutableRel> targetInputs = new ArrayList<>(target.getInputs());
+
+      final RexBuilder rexBuilder = call.getCluster().getRexBuilder();
+
+      for (MutableRel rel: query.getInputs()) {
+        if (rel instanceof MutableCalc) {
+          queryInputs.add((MutableCalc) rel);
+          queryGrandInputs.add(((MutableCalc) rel).getInput());
+        } else {
+          return null;
+        }
+      }
+
+      if (query.isAll() && target.isAll()
+          && sameRelCollectionNoOrderConsidered(queryGrandInputs, 
targetInputs)) {
+        Pair<RexNode, List<RexNode>> queryInputExplained0 =
+            explainCalc(queryInputs.get(0));
+        for (int i = 1; i < queryGrandInputs.size(); i++) {
+          Pair<RexNode, List<RexNode>> queryInputExplained =
+              explainCalc(queryInputs.get(i));
+          // Matching fails when filtering conditions are not equal or 
projects are not equal.
+          if (!splitFilter(call.getSimplify(), queryInputExplained0.left,
+              queryInputExplained.left).isAlwaysTrue()) {
+            return null;
+          }
+          for (Pair<RexNode, RexNode> pair : Pair.zip(
+              queryInputExplained0.right, queryInputExplained.right)) {
+            if (!pair.left.equals(pair.right)) {
+              return null;
+            }
+          }
+        }
+        RexProgram compenRexProgram = RexProgram.create(
+            target.rowType, queryInputExplained0.right, 
queryInputExplained0.left,
+            query.rowType, rexBuilder);
+        MutableCalc compenCalc = MutableCalc.of(target, compenRexProgram);
+        return tryMergeParentCalcAndGenResult(call, compenCalc);
+      }
+
+      return null;
+    }
+  }
+
+  /** Check if list0 and list1 contains the same nodes -- order is not 
considered. */
+  private static boolean sameRelCollectionNoOrderConsidered(
+      List<MutableRel> list0, List<MutableRel> list1) {
+    if (list0.size() != list1.size()) {
+      return false;
+    }
+    for (MutableRel rel: list0) {
+      int index = list1.indexOf(rel);
+      if (index == -1) {
+        return false;
+      } else {
+        list1.remove(index);
+      }
+    }
+    return true;
+  }
+
+  private static int fieldCnt(MutableRel rel) {
+    return rel.rowType.getFieldCount();
+  }
+
+  /** Explain filtering condition and projections from MutableCalc. */
+  private static Pair<RexNode, List<RexNode>> explainCalc(MutableCalc calc) {
+    final RexShuttle shuttle = getExpandShuttle(calc.program);
+    final RexNode condition = shuttle.apply(calc.program.getCondition());
+    final List<RexNode> projects = new ArrayList<>();
+    for (RexNode rex: shuttle.apply(calc.program.getProjectList())) {
+      projects.add(rex);
+    }
+    if (condition == null) {
+      return Pair.of(calc.cluster.getRexBuilder().makeLiteral(true), projects);
+    } else {
+      return Pair.of(condition, projects);
+    }
+  }
+
+  /**
+   * Generate result by merging parent and child if they are both MutableCalc.
+   * Otherwise result is the child itself.
+   */
+  private static UnifyResult tryMergeParentCalcAndGenResult(
+      UnifyRuleCall call, MutableRel child) {
+    MutableRel parent = call.query.getParent();
+    if (child instanceof MutableCalc && parent instanceof MutableCalc) {
+      MutableCalc mergedCalc = mergeCalc(call.getCluster().getRexBuilder(),
+          (MutableCalc) parent, (MutableCalc) child);
+      if (mergedCalc != null) {
+        // Note that property of stopTrying in the result is false
+        // and this query node deserves further matching iterations.
+        return call.create(parent).result(mergedCalc, false);
+      }
+    }
+    return call.result(child);
+  }
+
+  /** Merge two MutableCalc together. */
+  private static MutableCalc mergeCalc(
+      RexBuilder rexBuilder, MutableCalc topCalc, MutableCalc bottomCalc) {
+    RexProgram topProgram = topCalc.program;
+    if (RexOver.containsOver(topProgram)) {
+      return null;
+    }
+
+    RexProgram mergedProgram =
+        RexProgramBuilder.mergePrograms(
+            topCalc.program,
+            bottomCalc.program,
+            rexBuilder);
+    assert mergedProgram.getOutputRowType()
+        == topProgram.getOutputRowType();
+    return MutableCalc.of(bottomCalc.getInput(), mergedProgram);
+  }
+
+  private static RexShuttle getExpandShuttle(RexProgram rexProgram) {
+    return new RexShuttle() {
+      @Override public RexNode visitLocalRef(RexLocalRef localRef) {
+        return rexProgram.expandLocalRef(localRef);
+      }
+    };
+  }
+
+  /** Check if condition cond0 implies cond1. */
+  private static boolean implies(
+      RelOptCluster cluster, RexNode cond0, RexNode cond1, RelDataType 
rowType) {
+    RexExecutorImpl rexImpl =
+        (RexExecutorImpl) (cluster.getPlanner().getExecutor());
+    RexImplicationChecker rexImplicationChecker =
+        new RexImplicationChecker(cluster.getRexBuilder(), rexImpl, rowType);
+    return rexImplicationChecker.implies(cond0, cond1);
+  }
+
+  /** Check if join condition only references RexInputRef. */
+  private static boolean referenceByMapping(
+      RexNode joinCondition, List<RexNode>... projectsOfInputs) {
+    List<RexNode> projects = new ArrayList<>();
+    for (List<RexNode> projectsOfInput: projectsOfInputs) {
+      projects.addAll(projectsOfInput);
+    }
+
+    try {
+      RexVisitor rexVisitor = new RexVisitorImpl<Void>(true) {
+        @Override public Void visitInputRef(RexInputRef inputRef) {
+          if (!(projects.get(inputRef.getIndex()) instanceof RexInputRef)) {
+            throw new Util.FoundOne(inputRef);
 
 Review comment:
   I think it is a good candidate to use `Util.FoundOne.NULL`. What do you 
think, @julianhyde ?

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


With regards,
Apache Git Services

Reply via email to