5151import org .apache .calcite .util .graph .Graphs ;
5252import org .apache .calcite .util .graph .TopologicalOrderIterator ;
5353
54+ import com .google .common .collect .HashMultimap ;
5455import com .google .common .collect .ImmutableList ;
56+ import com .google .common .collect .Multimap ;
5557
5658import org .checkerframework .checker .nullness .qual .Nullable ;
5759
6668import java .util .Map ;
6769import java .util .Queue ;
6870import java .util .Set ;
71+ import java .util .stream .Collectors ;
6972
7073import static com .google .common .base .Preconditions .checkArgument ;
7174
@@ -114,6 +117,30 @@ public class HepPlanner extends AbstractRelOptPlanner {
114117 private final List <RelOptMaterialization > materializations =
115118 new ArrayList <>();
116119
120+ /**
121+ * Cache of rules that have already been fired for a specific operand match,
122+ * to avoid firing the same rule repeatedly.
123+ *
124+ * <p>Key: the list of matched {@link RelNode} IDs (operand match).
125+ *
126+ * <p>Value: the set of {@link RelOptRule}s already fired for that exact ID list.
127+ */
128+ private final Multimap <List <Integer >, RelOptRule > firedRulesCache = HashMultimap .create ();
129+
130+ /**
131+ * Reverse index for {@link #firedRulesCache}, used for cleanup/GC:
132+ * maps a single {@link RelNode} ID to all match-key ID lists that include it,
133+ * so related cache entries can be removed efficiently when a node is discarded.
134+ *
135+ * <p>Key: {@link RelNode} ID.
136+ *
137+ * <p>Value: match-key ID lists in {@link #firedRulesCache} that contain the key ID.
138+ */
139+ private final Multimap <Integer , List <Integer >> firedRulesCacheIndex = HashMultimap .create ();
140+
141+
142+ private boolean enableFiredRulesCache = false ;
143+
117144 //~ Constructors -----------------------------------------------------------
118145
119146 /**
@@ -173,6 +200,8 @@ public HepPlanner(
173200 removeRule (rule );
174201 }
175202 this .materializations .clear ();
203+ this .firedRulesCache .clear ();
204+ this .firedRulesCacheIndex .clear ();
176205 }
177206
178207 @ Override public RelNode changeTraits (RelNode rel , RelTraitSet toTraits ) {
@@ -195,6 +224,17 @@ public HepPlanner(
195224 return buildFinalPlan (requireNonNull (root , "'root' must not be null" ));
196225 }
197226
227+ /**
228+ * Enables or disables the fire-rule cache.
229+ *
230+ * <p> If enabled, a rule will not fire twice on the same {@code RelNode::getId()}.
231+ *
232+ * @param enable true to enable; false is default value.
233+ */
234+ public void setEnableFiredRulesCache (boolean enable ) {
235+ enableFiredRulesCache = enable ;
236+ }
237+
198238 /** Top-level entry point for a program. Initializes state and then invokes
199239 * the program. */
200240 private void executeProgram (HepProgram program ) {
@@ -519,13 +559,28 @@ private Iterator<HepRelVertex> getGraphIterator(
519559 nodeChildren ,
520560 parents );
521561
562+ List <Integer > relIds = null ;
563+ if (enableFiredRulesCache ) {
564+ relIds = call .getRelList ().stream ().map (RelNode ::getId ).collect (Collectors .toList ());
565+ if (firedRulesCache .get (relIds ).contains (rule )) {
566+ return null ;
567+ }
568+ }
569+
522570 // Allow the rule to apply its own side-conditions.
523571 if (!rule .matches (call )) {
524572 return null ;
525573 }
526574
527575 fireRule (call );
528576
577+ if (relIds != null ) {
578+ firedRulesCache .put (relIds , rule );
579+ for (Integer relId : relIds ) {
580+ firedRulesCacheIndex .put (relId , relIds );
581+ }
582+ }
583+
529584 if (!call .getResults ().isEmpty ()) {
530585 return applyTransformationResults (
531586 vertex ,
@@ -982,6 +1037,15 @@ private void collectGarbage() {
9821037
9831038 // Clean up metadata cache too.
9841039 sweepSet .forEach (this ::clearCache );
1040+
1041+ if (enableFiredRulesCache ) {
1042+ sweepSet .forEach (rel -> {
1043+ for (List <Integer > relIds : firedRulesCacheIndex .get (rel .getCurrentRel ().getId ())) {
1044+ firedRulesCache .removeAll (relIds );
1045+ }
1046+ firedRulesCacheIndex .removeAll (rel .getCurrentRel ().getId ());
1047+ });
1048+ }
9851049 }
9861050
9871051 private void assertNoCycles () {
0 commit comments