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

mihaibudiu 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 961310e814 [CALCITE-7794] Reduce the work HepPlanner does per rule 
application attempt
961310e814 is described below

commit 961310e814d8f1b80c4366d46b67929e01fe9428
Author: Michael Braun <[email protected]>
AuthorDate: Tue Sep 29 18:00:42 2026 +0000

    [CALCITE-7794] Reduce the work HepPlanner does per rule application attempt
    
    Rule application attempts greatly outnumber matches, so applyRule now tests
    the rule's operand before doing any other work. An attempt that cannot match
    skips the ConverterRule and CommonRelSubExprRule checks and the bindings and
    node-children allocations. The CommonRelSubExprRule parent-vertex to RelNode
    conversion moves after the operand test, so a non-matching rel no longer 
pays
    for it.
    
    The graph-membership check moves to the two callers, which try every rule
    against the same vertex and so need it only once. applyRules returns early
    when there is no rule at all.
    
    matchOperands is split so that applyRule, which has already tested the
    operand, calls matchOperandsForMatchedRel instead of testing it twice. Both
    methods now have javadoc. Loops over inputs and parents use indexed access,
    since getInputs() on a SingleRel allocates a new list on each call.
    
    Adds HepQueryVarietyBenchmark, which plans a range of query shapes through
    rules that fire, over varying numbers of rule collections and rules per
    collection, so planning changes can be measured against realistic queries.
    It includes COMMON_SUBEXPR (the CommonRelSubExprRule parent lookup) and
    UNION_PRUNE_EMPTY (the UNORDERED child-policy search).
    
    Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
---
 .../org/apache/calcite/plan/hep/HepPlanner.java    | 142 +++++++----
 .../benchmarks/HepQueryVarietyBenchmark.java       | 269 +++++++++++++++++++++
 2 files changed, 361 insertions(+), 50 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/plan/hep/HepPlanner.java 
b/core/src/main/java/org/apache/calcite/plan/hep/HepPlanner.java
index 6952fedb0a..cdd1f2ef11 100644
--- a/core/src/main/java/org/apache/calcite/plan/hep/HepPlanner.java
+++ b/core/src/main/java/org/apache/calcite/plan/hep/HepPlanner.java
@@ -42,7 +42,6 @@
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.util.ImmutableIntList;
-import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
 import org.apache.calcite.util.graph.BreadthFirstIterator;
 import org.apache.calcite.util.graph.CycleDetector;
@@ -478,6 +477,10 @@ private int depthFirstApply(HepProgram.State programState,
       boolean forceConversions, int nMatches) {
     while (iter.hasNext()) {
       HepRelVertex vertex = iter.next();
+      // Once per vertex: a match ends the loop below, so membership cannot 
change in it.
+      if (!graph.vertexSet().contains(vertex)) {
+        continue;
+      }
       for (RelOptRule rule : rules) {
         HepRelVertex newVertex =
             applyRule(rule, vertex, forceConversions);
@@ -514,6 +517,11 @@ private void applyRules(HepProgram.State programState,
 
     LOGGER.trace("Applying rule set {}", rules);
 
+    if (rules.isEmpty()) {
+      // Also skips getGraphIterator's gc for TOP_DOWN and BOTTOM_UP; nothing 
can transform.
+      return;
+    }
+
     final boolean fullRestartAfterTransformation =
         programState.matchOrder != HepMatchOrder.ARBITRARY
             && programState.matchOrder != HepMatchOrder.DEPTH_FIRST;
@@ -534,6 +542,10 @@ private void applyRules(HepProgram.State programState,
       fixedPoint = true;
       while (iter.hasNext()) {
         HepRelVertex vertex = iter.next();
+        // Once per vertex: a match ends the loop below, so membership cannot 
change in it.
+        if (!graph.vertexSet().contains(vertex)) {
+          continue;
+        }
         for (RelOptRule rule : rules) {
           HepRelVertex newVertex =
               applyRule(rule, vertex, forceConversions);
@@ -606,15 +618,17 @@ private Iterator<HepRelVertex> getGraphIterator(
     }
   }
 
+  /** Applies {@code rule} to {@code vertex}, which the caller must have 
verified is in
+   * {@link #graph}. */
   private @Nullable HepRelVertex applyRule(
       RelOptRule rule,
       HepRelVertex vertex,
       boolean forceConversions) {
-    if (!graph.vertexSet().contains(vertex)) {
-      return null;
-    }
+    final RelOptRuleOperand operand = rule.getOperand();
+    final RelNode currentRel = vertex.getCurrentRel();
+
     RelTrait parentTrait = null;
-    List<RelNode> parents = null;
+    List<HepRelVertex> parentVertices = null;
     if (rule instanceof ConverterRule) {
       // Guaranteed converter rules require special casing to make sure
       // they only fire where actually needed, otherwise they tend to
@@ -629,26 +643,27 @@ private Iterator<HepRelVertex> getGraphIterator(
     } else if (rule instanceof CommonRelSubExprRule) {
       // Only fire CommonRelSubExprRules if the vertex is a common
       // subexpression.
-      List<HepRelVertex> parentVertices = getVertexParents(vertex);
+      parentVertices = getVertexParents(vertex);
       if (parentVertices.size() < 2) {
         return null;
       }
-      parents = new ArrayList<>();
-      for (HepRelVertex pVertex : parentVertices) {
-        parents.add(pVertex.getCurrentRel());
+    }
+
+    if (!operand.matches(currentRel)) {
+      return null;
+    }
+
+    List<RelNode> parents = null;
+    if (parentVertices != null) {
+      parents = new ArrayList<>(parentVertices.size());
+      for (int i = 0; i < parentVertices.size(); i++) {
+        parents.add(parentVertices.get(i).getCurrentRel());
       }
     }
 
     final List<RelNode> bindings = new ArrayList<>();
     final Map<RelNode, List<RelNode>> nodeChildren = new HashMap<>();
-    boolean match =
-        matchOperands(
-            rule.getOperand(),
-            vertex.getCurrentRel(),
-            bindings,
-            nodeChildren);
-
-    if (!match) {
+    if (!matchOperandsForMatchedRel(operand, currentRel, bindings, 
nodeChildren)) {
       return null;
     }
 
@@ -703,8 +718,8 @@ private boolean doesConverterApply(
       HepRelVertex vertex) {
     RelTrait outTrait = converterRule.getOutTrait();
     List<HepRelVertex> parents = Graphs.predecessorListOf(graph, vertex);
-    for (HepRelVertex parent : parents) {
-      RelNode parentRel = parent.getCurrentRel();
+    for (int i = 0; i < parents.size(); i++) {
+      RelNode parentRel = parents.get(i).getCurrentRel();
       if (parentRel instanceof Converter) {
         // We don't support converter chains.
         continue;
@@ -732,10 +747,12 @@ protected List<HepRelVertex> 
getVertexParents(HepRelVertex vertex) {
     final List<HepRelVertex> parentVertices =
         Graphs.predecessorListOf(graph, vertex);
 
-    for (HepRelVertex pVertex : parentVertices) {
+    for (int j = 0; j < parentVertices.size(); j++) {
+      HepRelVertex pVertex = parentVertices.get(j);
       RelNode parent = pVertex.getCurrentRel();
-      for (int i = 0; i < parent.getInputs().size(); i++) {
-        HepRelVertex child = (HepRelVertex) parent.getInputs().get(i);
+      final List<RelNode> inputs = parent.getInputs();
+      for (int i = 0; i < inputs.size(); i++) {
+        HepRelVertex child = (HepRelVertex) inputs.get(i);
         if (child == vertex) {
           parents.add(pVertex);
         }
@@ -744,16 +761,40 @@ protected List<HepRelVertex> 
getVertexParents(HepRelVertex vertex) {
     return parents;
   }
 
+  /**
+   * Returns whether {@code rel} and its descendants match {@code operand} and 
its child
+   * operands.
+   *
+   * <p>A rel matches if the operand matches it by class, trait and predicate, 
and each child
+   * operand matches one of its children: the one in the same position, or any 
child if the
+   * policy is {@code UNORDERED}.
+   *
+   * <p>Adds {@code rel} to {@code bindings} once it matches {@code operand}, 
then checks its
+   * children. For an {@code UNORDERED} operand, also records the rel's 
children in
+   * {@code nodeChildren}.
+   */
   private static boolean matchOperands(
       RelOptRuleOperand operand,
       RelNode rel,
       List<RelNode> bindings,
       Map<RelNode, List<RelNode>> nodeChildren) {
-    if (!operand.matches(rel)) {
-      return false;
-    }
-    for (RelNode input : rel.getInputs()) {
-      if (!(input instanceof HepRelVertex)) {
+    return operand.matches(rel)
+        && matchOperandsForMatchedRel(operand, rel, bindings, nodeChildren);
+  }
+
+  /**
+   * Like {@link #matchOperands}, but assumes {@code operand} already matches 
{@code rel}.
+   * Checks only the inputs and the child operands.
+   */
+  private static boolean matchOperandsForMatchedRel(
+      RelOptRuleOperand operand,
+      RelNode rel,
+      List<RelNode> bindings,
+      Map<RelNode, List<RelNode>> nodeChildren) {
+    final List<RelNode> inputs = rel.getInputs();
+    final int inputSize = inputs.size();
+    for (int i = 0; i < inputSize; i++) {
+      if (!(inputs.get(i) instanceof HepRelVertex)) {
         // The graph could be partially optimized for materialized view. In 
that
         // case, the input would be a RelNode and shouldn't be matched again 
here.
         return false;
@@ -761,7 +802,7 @@ private static boolean matchOperands(
     }
     bindings.add(rel);
     @SuppressWarnings("unchecked")
-    List<HepRelVertex> childRels = (List) rel.getInputs();
+    List<HepRelVertex> childRels = (List) inputs;
     switch (operand.childPolicy) {
     case ANY:
       return true;
@@ -770,11 +811,11 @@ private static boolean matchOperands(
       // matchAnyChildren, usually there's just one operand.
       for (RelOptRuleOperand childOperand : operand.getChildOperands()) {
         boolean match = false;
-        for (HepRelVertex childRel : childRels) {
+        for (int i = 0; i < inputSize; i++) {
           match =
               matchOperands(
                   childOperand,
-                  childRel.getCurrentRel(),
+                  childRels.get(i).getCurrentRel(),
                   bindings,
                   nodeChildren);
           if (match) {
@@ -785,26 +826,24 @@ private static boolean matchOperands(
           return false;
         }
       }
-      final List<RelNode> children = new ArrayList<>(childRels.size());
-      for (HepRelVertex childRel : childRels) {
-        children.add(childRel.getCurrentRel());
+      final List<RelNode> children = new ArrayList<>(inputSize);
+      for (int i = 0; i < inputSize; i++) {
+        children.add(childRels.get(i).getCurrentRel());
       }
       nodeChildren.put(rel, children);
       return true;
     default:
-      int n = operand.getChildOperands().size();
-      if (childRels.size() < n) {
+      final List<RelOptRuleOperand> childOperands = operand.getChildOperands();
+      final int n = childOperands.size();
+      if (inputSize < n) {
         return false;
       }
-      for (Pair<HepRelVertex, RelOptRuleOperand> pair
-          : Pair.zip(childRels, operand.getChildOperands())) {
-        boolean match =
-            matchOperands(
-                pair.right,
-                pair.left.getCurrentRel(),
-                bindings,
-                nodeChildren);
-        if (!match) {
+      for (int i = 0; i < n; i++) {
+        if (!matchOperands(
+            childOperands.get(i),
+            childRels.get(i).getCurrentRel(),
+            bindings,
+            nodeChildren)) {
           return false;
         }
       }
@@ -862,7 +901,8 @@ private HepRelVertex applyTransformationResults(
     final List<HepRelVertex> allParents =
         Graphs.predecessorListOf(graph, vertex);
     final List<HepRelVertex> parents = new ArrayList<>();
-    for (HepRelVertex parent : allParents) {
+    for (int i = 0; i < allParents.size(); i++) {
+      final HepRelVertex parent = allParents.get(i);
       if (parentTrait != null) {
         RelNode parentRel = parent.getCurrentRel();
         if (parentRel instanceof Converter) {
@@ -952,9 +992,10 @@ private HepRelVertex addRelToGraph(
     // Recursively add children, replacing this rel's inputs
     // with corresponding child vertices.
     final List<RelNode> inputs = rel.getInputs();
-    final List<RelNode> newInputs = new ArrayList<>();
-    for (RelNode input1 : inputs) {
-      HepRelVertex childVertex = addRelToGraph(input1, initRelToVertexCache);
+    final int inputCount = inputs.size();
+    final List<RelNode> newInputs = new ArrayList<>(inputCount);
+    for (int i = 0; i < inputCount; i++) {
+      HepRelVertex childVertex = addRelToGraph(inputs.get(i), 
initRelToVertexCache);
       newInputs.add(childVertex);
     }
 
@@ -982,8 +1023,9 @@ private HepRelVertex addRelToGraph(
     graph.addVertex(newVertex);
     updateVertex(newVertex, rel);
 
-    for (RelNode input : rel.getInputs()) {
-      graph.addEdge(newVertex, (HepRelVertex) input);
+    final List<RelNode> vertexInputs = rel.getInputs();
+    for (int i = 0; i < vertexInputs.size(); i++) {
+      graph.addEdge(newVertex, (HepRelVertex) vertexInputs.get(i));
     }
 
     if (initRelToVertexCache != null) {
diff --git 
a/ubenchmark/src/jmh/java/org/apache/calcite/benchmarks/HepQueryVarietyBenchmark.java
 
b/ubenchmark/src/jmh/java/org/apache/calcite/benchmarks/HepQueryVarietyBenchmark.java
new file mode 100644
index 0000000000..17194ceed4
--- /dev/null
+++ 
b/ubenchmark/src/jmh/java/org/apache/calcite/benchmarks/HepQueryVarietyBenchmark.java
@@ -0,0 +1,269 @@
+/*
+ * 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.benchmarks;
+
+import org.apache.calcite.plan.RelOptRule;
+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.rel.RelNode;
+import org.apache.calcite.rel.logical.LogicalUnion;
+import org.apache.calcite.rel.logical.LogicalValues;
+import org.apache.calcite.rel.rules.CommonRelSubExprRegisterRule;
+import org.apache.calcite.rel.rules.CoreRules;
+import org.apache.calcite.rel.rules.PruneEmptyRules;
+import org.apache.calcite.test.Fixtures;
+
+import com.google.common.collect.ImmutableList;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Threads;
+import org.openjdk.jmh.annotations.Warmup;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Benchmarks {@code HepPlanner.setRoot} and {@code findBestExp} over a range 
of query shapes.
+ * Queries are converted to {@link RelNode} once per trial, so only planning 
is measured.
+ */
+@Fork(value = 1, jvmArgsPrepend = {"-Xss200m",
+    "-Dcalcite.disable.generate.type.digest.string=true"})
+@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
+@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+@State(Scope.Thread)
+@Threads(1)
+public class HepQueryVarietyBenchmark {
+
+  /** Backstop should a rule set stop reaching a fixpoint; these queries never 
reach it. */
+  private static final int MATCH_LIMIT = 1000;
+
+  @Param({"FILTER_PROJECT", "AGGREGATE", "CORRELATED_EXISTS", "JOIN5", 
"MIXED_DEEP",
+      "UNION_WIDE", "COMMON_SUBEXPR", "UNION_PRUNE_EMPTY"})
+  String query;
+
+  /** Collections the rules are split across, one {@code addRuleCollection} 
call each. */
+  @Param({"1", "5", "10"})
+  int ruleCollections;
+
+  /** Rules in each collection; one is what {@code addRuleInstance} builds. */
+  @Param({"1", "5", "10", "50"})
+  int rulesPerCollection;
+
+  /** Share of the rule set able to rewrite these queries, not the share of 
attempts matching. */
+  @Param({"0", "50", "100"})
+  int firingPercent;
+
+  /** Rules that rewrite these queries. No rule's inverse is present; a pair 
never converges. */
+  private static final List<RelOptRule> FIRING =
+      ImmutableList.of(
+          CoreRules.FILTER_INTO_JOIN,
+          CoreRules.FILTER_PROJECT_TRANSPOSE,
+          CoreRules.FILTER_MERGE,
+          CoreRules.FILTER_AGGREGATE_TRANSPOSE,
+          CoreRules.PROJECT_MERGE,
+          CoreRules.PROJECT_REMOVE,
+          CoreRules.AGGREGATE_PROJECT_MERGE,
+          CoreRules.AGGREGATE_REMOVE,
+          CoreRules.JOIN_CONDITION_PUSH,
+          CoreRules.JOIN_PUSH_EXPRESSIONS,
+          CoreRules.SORT_PROJECT_TRANSPOSE,
+          CoreRules.UNION_MERGE,
+          CoreRules.CALC_MERGE);
+
+  /** Real rules no query here can fire: none has INTERSECT, MINUS, MATCH, 
SAMPLE or exchange. */
+  private static final List<RelOptRule> NON_APPLICABLE =
+      ImmutableList.of(
+          CoreRules.INTERSECT_MERGE,
+          CoreRules.INTERSECT_REMOVE,
+          CoreRules.INTERSECT_REORDER,
+          CoreRules.INTERSECT_TO_DISTINCT,
+          CoreRules.INTERSECT_TO_EXISTS,
+          CoreRules.INTERSECT_TO_SEMI_JOIN,
+          CoreRules.INTERSECT_FILTER_TO_FILTER,
+          CoreRules.MINUS_MERGE,
+          CoreRules.MINUS_REMOVE,
+          CoreRules.MINUS_TO_DISTINCT,
+          CoreRules.MINUS_TO_ANTI_JOIN,
+          CoreRules.MINUS_FILTER_TO_FILTER,
+          CoreRules.SAMPLE_TO_FILTER,
+          CoreRules.EXCHANGE_REMOVE_CONSTANT_KEYS,
+          CoreRules.MATCH);
+
+  /** Rules that reach the common-subexpression path; none calls {@code 
transformTo}. */
+  private static final List<RelOptRule> COMMON_SUB_EXPR =
+      ImmutableList.of(
+          CommonRelSubExprRegisterRule.Config.FILTER.toRule(),
+          CommonRelSubExprRegisterRule.Config.PROJECT.toRule(),
+          CommonRelSubExprRegisterRule.Config.JOIN.toRule(),
+          CommonRelSubExprRegisterRule.Config.AGGREGATE.toRule());
+
+  /** One of three core rules with an {@code UNORDERED} child policy. */
+  private static final List<RelOptRule> UNORDERED_RULE =
+      ImmutableList.of(PruneEmptyRules.UNION_INSTANCE);
+
+  private static String sqlFor(String name) {
+    switch (name) {
+    case "FILTER_PROJECT":
+      return "select empno, ename, sal + comm as total from emp\n"
+          + "where deptno = 10 and sal > 1000 and job <> 'CLERK'";
+    case "JOIN5":
+      return "select e1.ename, e2.ename, d1.name, d2.name, s.grade\n"
+          + "from emp e1\n"
+          + "join emp e2 on e1.mgr = e2.empno\n"
+          + "join dept d1 on e1.deptno = d1.deptno\n"
+          + "join dept d2 on e2.deptno = d2.deptno\n"
+          + "join salgrade s on e1.sal between s.losal and s.hisal\n"
+          + "where d1.deptno <> d2.deptno";
+    case "AGGREGATE":
+      return "select deptno, job, count(*) as c, sum(sal) as s, avg(sal) as 
a\n"
+          + "from emp where sal > 500\n"
+          + "group by deptno, job having sum(sal) > 1000";
+    case "CORRELATED_EXISTS":
+      return "select e.ename from emp e\n"
+          + "where exists (select 1 from dept d where d.deptno = e.deptno and 
d.name <> 'X')";
+    case "MIXED_DEEP":
+      return "select t.deptno, t.c, d.name from\n"
+          + "  (select deptno, count(*) as c from\n"
+          + "     (select * from emp where sal > 100 and job <> 'CLERK') x\n"
+          + "   where x.deptno > 5 group by deptno) t\n"
+          + "join dept d on t.deptno = d.deptno\n"
+          + "where t.c > 1 order by t.c desc";
+    case "UNION_WIDE":
+      return wideUnion(60);
+    case "COMMON_SUBEXPR":
+      // Identical branches share a vertex, giving it the two parents this 
path requires.
+      return "select t1.deptno, t1.s, t2.s from\n"
+          + "  (select deptno, sum(sal) as s from emp where sal > 100 group by 
deptno) t1\n"
+          + "join\n"
+          + "  (select deptno, sum(sal) as s from emp where sal > 100 group by 
deptno) t2\n"
+          + "on t1.deptno = t2.deptno";
+    default:
+      throw new IllegalArgumentException("unknown query: " + name);
+    }
+  }
+
+  /**
+   * Returns a UNION ALL of {@code branches} filtered scans, giving a plan of 
a few hundred
+   * nodes rather than the few dozen the hand-written queries above produce.
+   */
+  private static String wideUnion(int branches) {
+    StringBuilder b = new StringBuilder();
+    for (int i = 0; i < branches; i++) {
+      if (i > 0) {
+        b.append("union all\n");
+      }
+      b.append("select ename, sal from emp where deptno = ").append(i)
+          .append(" and sal > ").append(i).append("\n");
+    }
+    return b.toString();
+  }
+
+  /**
+   * Returns a union of 60 real branches plus one empty {@code Values}, built 
directly with
+   * {@link LogicalUnion#create} since SQL has no way to write a single wide 
union with a
+   * provably-empty branch.
+   */
+  private static RelNode unionPruneEmptyRoot() {
+    final List<RelNode> branches = new ArrayList<>(61);
+    for (int i = 0; i < 60; i++) {
+      branches.add(
+          Fixtures.forSqlToRel()
+              .withSql("select ename, sal from emp where deptno = " + i + " 
and sal > " + i)
+              .toRel());
+    }
+    branches.add(
+        LogicalValues.createEmpty(branches.get(0).getCluster(), 
branches.get(0).getRowType()));
+    return LogicalUnion.create(branches, true);
+  }
+
+  private RelNode root;
+  private HepProgram program;
+
+  @Setup(Level.Trial)
+  public void setup() {
+    root = "UNION_PRUNE_EMPTY".equals(query)
+        ? unionPruneEmptyRoot()
+        : Fixtures.forSqlToRel().withSql(sqlFor(query)).toRel();
+
+    HepProgramBuilder builder = HepProgram.builder()
+        .addMatchLimit(MATCH_LIMIT)
+        .addMatchOrder(HepMatchOrder.ARBITRARY);
+    for (List<RelOptRule> collection : collections()) {
+      builder.addRuleCollection(collection);
+    }
+    program = builder.build();
+  }
+
+  /**
+   * Returns {@link #ruleCollections} collections of {@link 
#rulesPerCollection} rules,
+   * {@code firingPercent} of them from {@link #FIRING}, spread evenly so that 
every
+   * collection holds a similar mix rather than the firing rules landing in 
the first few.
+   */
+  private List<List<RelOptRule>> collections() {
+    final int total = ruleCollections * rulesPerCollection;
+    final int firing = total * firingPercent / 100;
+    final List<RelOptRule> nonFiring;
+    switch (query) {
+    case "COMMON_SUBEXPR":
+      nonFiring = COMMON_SUB_EXPR;
+      break;
+    case "UNION_PRUNE_EMPTY":
+      nonFiring = UNORDERED_RULE;
+      break;
+    default:
+      nonFiring = NON_APPLICABLE;
+    }
+    List<RelOptRule> all = new ArrayList<>(total);
+    int f = 0;
+    int n = 0;
+    for (int i = 0; i < total; i++) {
+      if ((i + 1) * firing / total > i * firing / total) {
+        all.add(FIRING.get(f++ % FIRING.size()));
+      } else {
+        all.add(nonFiring.get(n++ % nonFiring.size()));
+      }
+    }
+    List<List<RelOptRule>> split = new ArrayList<>(ruleCollections);
+    for (int i = 0; i < ruleCollections; i++) {
+      split.add(all.subList(i * rulesPerCollection, (i + 1) * 
rulesPerCollection));
+    }
+    return split;
+  }
+
+  @Benchmark
+  public RelNode plan() {
+    HepPlanner planner = new HepPlanner(program);
+    planner.setLargePlanMode(true);
+    planner.setRoot(root);
+    return planner.findBestExp();
+  }
+}

Reply via email to