diff --git a/core/src/main/java/org/apache/calcite/tools/Programs.java b/core/src/main/java/org/apache/calcite/tools/Programs.java index 83f6834d2850..4d795d554902 100644 --- a/core/src/main/java/org/apache/calcite/tools/Programs.java +++ b/core/src/main/java/org/apache/calcite/tools/Programs.java @@ -21,6 +21,7 @@ import org.apache.calcite.config.CalciteSystemProperty; import org.apache.calcite.plan.RelOptCostImpl; import org.apache.calcite.plan.RelOptLattice; +import org.apache.calcite.plan.RelOptListener; import org.apache.calcite.plan.RelOptMaterialization; import org.apache.calcite.plan.RelOptPlanner; import org.apache.calcite.plan.RelOptRule; @@ -53,6 +54,8 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.Arrays; import java.util.List; import java.util.function.Predicate; @@ -190,6 +193,41 @@ public static Program of(final HepProgram hepProgram, final boolean noDag, return hepPlanner.findBestExp(); }; } + /** Creates a program that executes a {@link HepProgram}. + * + *
If {@code listener} is not null, it is
+ * {@linkplain HepPlanner#addListener(RelOptListener) attached}
+ * to the planner before execution. */
+ @SuppressWarnings("deprecation")
+ public static Program of(final HepProgram hepProgram, final boolean noDag,
+ final RelMetadataProvider metadataProvider,
+ final @Nullable RelOptListener listener) {
+ requireNonNull(metadataProvider, "metadataProvider");
+ if (listener == null) {
+ return of(hepProgram, noDag, metadataProvider);
+ }
+ return (planner, rel, requiredOutputTraits, materializations, lattices) -> {
+ final HepPlanner hepPlanner =
+ new HepPlanner(hepProgram, null, noDag, null, RelOptCostImpl.FACTORY);
+
+ hepPlanner.addListener(listener);
+
+ List Verifies that a {@link RelOptListener} passed to
+ * {@link Programs#of(HepProgram, boolean, RelMetadataProvider, RelOptListener)}
+ * receives rule-attempted events during execution. */
+ @Test void testProgramsOfWithListener() {
+ final HepTestListener listener = new HepTestListener(0);
+
+ final HepProgram program = new HepProgramBuilder()
+ .addRuleInstance(CoreRules.FILTER_TO_CALC)
+ .build();
+
+ final Program p =
+ Programs.of(program, true, DefaultRelMetadataProvider.INSTANCE, listener);
+
+ final String sql = "select 1 from dept where abs(-1) = 20";
+ final RelNode rel = sql(sql).toRel();
+
+ final RelNode result =
+ p.run(rel.getCluster().getPlanner(), rel, rel.getTraitSet(),
+ ImmutableList.of(), ImmutableList.of());
+
+ // The listener must have been notified at least once
+ assertThat(listener.getApplyTimes() > 0, is(true));
+ }
+
+ /** Tests that {@link Programs#of(HepProgram, boolean, RelMetadataProvider, RelOptListener)}
+ * with a null listener behaves identically to the overload without a listener. */
+ @Test void testProgramsOfWithNullListener() {
+ final HepProgram program = new HepProgramBuilder()
+ .addRuleInstance(CoreRules.FILTER_TO_CALC)
+ .build();
+
+ final Program p =
+ Programs.of(program, true, DefaultRelMetadataProvider.INSTANCE, null);
+
+ final String sql = "select 1 from dept where abs(-1) = 20";
+ final RelNode rel = sql(sql).toRel();
+
+ // Should not throw; null listener is safely ignored
+ final RelNode result =
+ p.run(rel.getCluster().getPlanner(), rel, rel.getTraitSet(),
+ ImmutableList.of(), ImmutableList.of());
+ assertThat(result, is(notNullValue()));
+ }
}
diff --git a/core/src/test/resources/org/apache/calcite/test/HepPlannerTest.xml b/core/src/test/resources/org/apache/calcite/test/HepPlannerTest.xml
index ce092dcedcb2..aa504480f32a 100644
--- a/core/src/test/resources/org/apache/calcite/test/HepPlannerTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/HepPlannerTest.xml
@@ -147,6 +147,16 @@ LogicalAggregate(group=[{0}])
+