Vladimir Sitnikov created CALCITE-7854:
------------------------------------------
Summary: FilterProjectTransposeRule throws NullPointerException
when the Filter has a distribution trait and the Project's input has none
Key: CALCITE-7854
URL: https://issues.apache.org/jira/browse/CALCITE-7854
Project: Calcite
Issue Type: Bug
Components: core
Reporter: Vladimir Sitnikov
Applying {{FilterProjectTransposeRule}} (for example
{{CoreRules.FILTER_PROJECT_TRANSPOSE}}, which copies the Filter) throws
{{NullPointerException}} when the Filter's trait set contains a distribution
trait and the trait set of the Project's input does not. Trait sets differ in
this way when the planner does not register {{RelDistributionTraitDef}} and a
distribution is added to the Filter with {{RelTraitSet#plus}}, as a
user-defined rule or Filter subclass can do.
Reproducer (for {{RelOptRulesTest}}; {{RelBuilderTest.config()}} uses a planner
that does not register {{RelDistributionTraitDef}}):
{code:java}
@Test void testFilterProjectTransposeRuleInputWithoutDistribution() {
final RelBuilder b = RelBuilder.create(RelBuilderTest.config().build());
final Filter filter = (Filter) b
.scan("EMP")
.project(b.field("SAL"))
.filter(b.equals(b.field(0), b.literal(11500)))
.build();
final RelNode in =
filter.copy(filter.getTraitSet().plus(RelDistributions.SINGLETON),
filter.getInput(), filter.getCondition());
final HepProgram program = new HepProgramBuilder()
.addRuleInstance(CoreRules.FILTER_PROJECT_TRANSPOSE)
.build();
final HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(in);
final RelNode result = hepPlanner.findBestExp();
final RelNode pushedFilter = result.getInput(0);
assertThat(
pushedFilter.getTraitSet().getTrait(RelDistributionTraitDef.INSTANCE),
is(RelDistributions.ANY));
}
{code}
Actual result on main (commit 8f27e24a02; 1.42.0 has the same code):
{noformat}
java.lang.NullPointerException: Cannot invoke
"org.apache.calcite.plan.RelTrait.getClass()" because "trait" is null
at org.apache.calcite.plan.RelTraitDef.canonize(RelTraitDef.java:105)
at org.apache.calcite.plan.RelCompositeTrait.of(RelCompositeTrait.java:64)
at org.apache.calcite.plan.RelTraitSet.replaceIfs(RelTraitSet.java:264)
at
org.apache.calcite.rel.rules.FilterProjectTransposeRule.onMatch(FilterProjectTransposeRule.java:188)
at
org.apache.calcite.plan.AbstractRelOptPlanner.fireRule(AbstractRelOptPlanner.java:380)
at org.apache.calcite.plan.hep.HepPlanner.applyRule(HepPlanner.java:697)
{noformat}
Expected result: the rule pushes the Filter below the Project, and the pushed
Filter has the default distribution, {{RelDistributions.ANY}}. That is what
{{RelTraitSet#replaceIf}} and {{RelTraitSet#replaceIfs}} produce when their
supplier returns null, and the rule already relies on that fallback for the
collation trait.
Cause: when {{copyFilter}} is true, the rule copies the input's distribution
with {{replaceIfs(RelDistributionTraitDef.INSTANCE, () ->
Collections.singletonList(input.getTraitSet().getTrait(RelDistributionTraitDef.INSTANCE)))}}.
{{getTrait}} returns null when the input's trait set has no distribution
trait, so the supplier returns a list that holds null rather than null itself,
and {{RelCompositeTrait.of}} fails on the null element. Calling {{replaceIf}},
which falls back to the default when the supplier returns null, fixes it.
Found while reviewing CALCITE-7736 ([PR
#5213|https://github.com/apache/calcite/pull/5213]), which carries this fix
inside the nullness migration.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)