diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/planner/Rules.java b/sql/src/main/java/org/apache/druid/sql/calcite/planner/Rules.java index 03b1a31c368..c9135d52757 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/planner/Rules.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/planner/Rules.java @@ -26,10 +26,13 @@ import org.apache.calcite.plan.RelOptMaterialization; import org.apache.calcite.plan.RelOptPlanner; import org.apache.calcite.plan.RelOptRule; import org.apache.calcite.plan.RelTraitSet; +import org.apache.calcite.plan.hep.HepProgram; +import org.apache.calcite.plan.hep.HepProgramBuilder; import org.apache.calcite.plan.volcano.AbstractConverter; import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.core.RelFactories; import org.apache.calcite.rel.metadata.DefaultRelMetadataProvider; +import org.apache.calcite.rel.metadata.RelMetadataProvider; import org.apache.calcite.rel.rules.AggregateCaseToFilterRule; import org.apache.calcite.rel.rules.AggregateExpandDistinctAggregatesRule; import org.apache.calcite.rel.rules.AggregateJoinTransposeRule; @@ -85,6 +88,16 @@ public class Rules public static final int DRUID_CONVENTION_RULES = 0; public static final int BINDABLE_CONVENTION_RULES = 1; + // Due to Calcite bug (CALCITE-3845), ReduceExpressionsRule can considered expression which is the same as the + // previous input expression as reduced. Basically, the expression is actually not reduced but is still considered as + // reduced. Hence, this resulted in an infinite loop of Calcite trying to reducing the same expression over and over. + // Calcite 1.23.0 fixes this issue by not consider expression as reduced if this case happens. However, while + // we are still using Calcite 1.21.0, a workaround is to limit the number of pattern matches to avoid infinite loop. + private static final String HEP_DEFAULT_MATCH_LIMIT_CONFIG_STRING = "druid.sql.planner.hepMatchLimit"; + private static final int HEP_DEFAULT_MATCH_LIMIT = Integer.valueOf( + System.getProperty(HEP_DEFAULT_MATCH_LIMIT_CONFIG_STRING, "1200") + ); + // Rules from RelOptUtil's registerBaseRules, minus: // // 1) AggregateExpandDistinctAggregatesRule (it'll be added back later if approximate count distinct is disabled) @@ -191,12 +204,14 @@ public class Rules public static List programs(final PlannerContext plannerContext, final QueryMaker queryMaker) { + + // Program that pre-processes the tree before letting the full-on VolcanoPlanner loose. final Program preProgram = Programs.sequence( Programs.subQuery(DefaultRelMetadataProvider.INSTANCE), DecorrelateAndTrimFieldsProgram.INSTANCE, - Programs.hep(REDUCTION_RULES, true, DefaultRelMetadataProvider.INSTANCE) + buildHepProgram(REDUCTION_RULES, true, DefaultRelMetadataProvider.INSTANCE, HEP_DEFAULT_MATCH_LIMIT) ); return ImmutableList.of( @@ -205,6 +220,19 @@ public class Rules ); } + private static Program buildHepProgram(Iterable rules, + boolean noDag, + RelMetadataProvider metadataProvider, + int matchLimit) + { + final HepProgramBuilder builder = HepProgram.builder(); + builder.addMatchLimit(matchLimit); + for (RelOptRule rule : rules) { + builder.addRuleInstance(rule); + } + return Programs.of(builder.build(), noDag, metadataProvider); + } + private static List druidConventionRuleSet( final PlannerContext plannerContext, final QueryMaker queryMaker diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java index 17c970fa138..483cdd285d4 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java @@ -139,6 +139,19 @@ public class CalciteQueryTest extends BaseCalciteQueryTest ); } + @Test + public void testExpressionContainingNull() throws Exception + { + List expectedResult = new ArrayList<>(); + expectedResult.add("Hello"); + expectedResult.add(null); + testQuery( + "SELECT ARRAY ['Hello', NULL]", + ImmutableList.of(), + ImmutableList.of(new Object[]{expectedResult}) + ); + } + @Test public void testSelectNonNumericNumberLiterals() throws Exception {