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)

Reply via email to