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 f181e079da [CALCITE-7416] Add firedRulesCache for HepPlanner
f181e079da is described below
commit f181e079da23cd0de19bdbb34110eb445280e905
Author: wenzhuang.zwz <[email protected]>
AuthorDate: Sat Feb 14 10:50:26 2026 +0800
[CALCITE-7416] Add firedRulesCache for HepPlanner
---
.../org/apache/calcite/plan/hep/HepPlanner.java | 64 ++++++++++++++++++++++
.../org/apache/calcite/test/HepPlannerTest.java | 29 ++++++++--
2 files changed, 88 insertions(+), 5 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 e9cb6da02a..c5f8b5610f 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
@@ -51,7 +51,9 @@
import org.apache.calcite.util.graph.Graphs;
import org.apache.calcite.util.graph.TopologicalOrderIterator;
+import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multimap;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -66,6 +68,7 @@
import java.util.Map;
import java.util.Queue;
import java.util.Set;
+import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkArgument;
@@ -114,6 +117,30 @@ public class HepPlanner extends AbstractRelOptPlanner {
private final List<RelOptMaterialization> materializations =
new ArrayList<>();
+ /**
+ * Cache of rules that have already been fired for a specific operand match,
+ * to avoid firing the same rule repeatedly.
+ *
+ * <p>Key: the list of matched {@link RelNode} IDs (operand match).
+ *
+ * <p>Value: the set of {@link RelOptRule}s already fired for that exact ID
list.
+ */
+ private final Multimap<List<Integer>, RelOptRule> firedRulesCache =
HashMultimap.create();
+
+ /**
+ * Reverse index for {@link #firedRulesCache}, used for cleanup/GC:
+ * maps a single {@link RelNode} ID to all match-key ID lists that include
it,
+ * so related cache entries can be removed efficiently when a node is
discarded.
+ *
+ * <p>Key: {@link RelNode} ID.
+ *
+ * <p>Value: match-key ID lists in {@link #firedRulesCache} that contain the
key ID.
+ */
+ private final Multimap<Integer, List<Integer>> firedRulesCacheIndex =
HashMultimap.create();
+
+
+ private boolean enableFiredRulesCache = false;
+
//~ Constructors -----------------------------------------------------------
/**
@@ -173,6 +200,8 @@ public HepPlanner(
removeRule(rule);
}
this.materializations.clear();
+ this.firedRulesCache.clear();
+ this.firedRulesCacheIndex.clear();
}
@Override public RelNode changeTraits(RelNode rel, RelTraitSet toTraits) {
@@ -195,6 +224,17 @@ public HepPlanner(
return buildFinalPlan(requireNonNull(root, "'root' must not be null"));
}
+ /**
+ * Enables or disables the fire-rule cache.
+ *
+ * <p> If enabled, a rule will not fire twice on the same {@code
RelNode::getId()}.
+ *
+ * @param enable true to enable; false is default value.
+ */
+ public void setEnableFiredRulesCache(boolean enable) {
+ enableFiredRulesCache = enable;
+ }
+
/** Top-level entry point for a program. Initializes state and then invokes
* the program. */
private void executeProgram(HepProgram program) {
@@ -519,6 +559,14 @@ private Iterator<HepRelVertex> getGraphIterator(
nodeChildren,
parents);
+ List<Integer> relIds = null;
+ if (enableFiredRulesCache) {
+ relIds =
call.getRelList().stream().map(RelNode::getId).collect(Collectors.toList());
+ if (firedRulesCache.get(relIds).contains(rule)) {
+ return null;
+ }
+ }
+
// Allow the rule to apply its own side-conditions.
if (!rule.matches(call)) {
return null;
@@ -526,6 +574,13 @@ private Iterator<HepRelVertex> getGraphIterator(
fireRule(call);
+ if (relIds != null) {
+ firedRulesCache.put(relIds, rule);
+ for (Integer relId : relIds) {
+ firedRulesCacheIndex.put(relId, relIds);
+ }
+ }
+
if (!call.getResults().isEmpty()) {
return applyTransformationResults(
vertex,
@@ -982,6 +1037,15 @@ private void collectGarbage() {
// Clean up metadata cache too.
sweepSet.forEach(this::clearCache);
+
+ if (enableFiredRulesCache) {
+ sweepSet.forEach(rel -> {
+ for (List<Integer> relIds :
firedRulesCacheIndex.get(rel.getCurrentRel().getId())) {
+ firedRulesCache.removeAll(relIds);
+ }
+ firedRulesCacheIndex.removeAll(rel.getCurrentRel().getId());
+ });
+ }
}
private void assertNoCycles() {
diff --git a/core/src/test/java/org/apache/calcite/test/HepPlannerTest.java
b/core/src/test/java/org/apache/calcite/test/HepPlannerTest.java
index 262bba0313..9af5e7fe83 100644
--- a/core/src/test/java/org/apache/calcite/test/HepPlannerTest.java
+++ b/core/src/test/java/org/apache/calcite/test/HepPlannerTest.java
@@ -366,11 +366,29 @@ private void assertIncludesExactlyOnce(String message,
String digest,
}
@Test void testRuleApplyCount() {
- final long applyTimes1 = checkRuleApplyCount(HepMatchOrder.ARBITRARY);
- assertThat(applyTimes1, is(316L));
+ long applyTimes = checkRuleApplyCount(HepMatchOrder.ARBITRARY, false);
+ assertThat(applyTimes, is(316L));
- final long applyTimes2 = checkRuleApplyCount(HepMatchOrder.DEPTH_FIRST);
- assertThat(applyTimes2, is(87L));
+ applyTimes = checkRuleApplyCount(HepMatchOrder.DEPTH_FIRST, false);
+ assertThat(applyTimes, is(87L));
+
+ applyTimes = checkRuleApplyCount(HepMatchOrder.TOP_DOWN, false);
+ assertThat(applyTimes, is(295L));
+
+ applyTimes = checkRuleApplyCount(HepMatchOrder.BOTTOM_UP, false);
+ assertThat(applyTimes, is(296L));
+
+ applyTimes = checkRuleApplyCount(HepMatchOrder.ARBITRARY, true);
+ assertThat(applyTimes, is(65L));
+
+ applyTimes = checkRuleApplyCount(HepMatchOrder.DEPTH_FIRST, true);
+ assertThat(applyTimes, is(65L));
+
+ applyTimes = checkRuleApplyCount(HepMatchOrder.TOP_DOWN, true);
+ assertThat(applyTimes, is(65L));
+
+ applyTimes = checkRuleApplyCount(HepMatchOrder.BOTTOM_UP, true);
+ assertThat(applyTimes, is(65L));
}
@Test void testMaterialization() {
@@ -387,7 +405,7 @@ private void assertIncludesExactlyOnce(String message,
String digest,
assertThat(planner.getMaterializations(), empty());
}
- private long checkRuleApplyCount(HepMatchOrder matchOrder) {
+ private long checkRuleApplyCount(HepMatchOrder matchOrder, boolean
enableFiredRulesCache) {
final HepProgramBuilder programBuilder = HepProgram.builder();
programBuilder.addMatchOrder(matchOrder);
programBuilder.addRuleInstance(CoreRules.FILTER_REDUCE_EXPRESSIONS);
@@ -397,6 +415,7 @@ private long checkRuleApplyCount(HepMatchOrder matchOrder) {
HepPlanner planner = new HepPlanner(programBuilder.build());
planner.addListener(listener);
planner.setRoot(sql(COMPLEX_UNION_TREE).toRel());
+ planner.setEnableFiredRulesCache(enableFiredRulesCache);
planner.findBestExp();
return listener.getApplyTimes();
}