[ 
https://issues.apache.org/jira/browse/CALCITE-5260?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

mengdou updated CALCITE-5260:
-----------------------------
    Description: 
First I define a pattern(For demonstration)  like this:
{code:java}
// pattern like this, using 'unordered' child policy for MultiJoin
operandJ(MultiJoin.class, ...
  unordered(
    operand(Project.class, ...
      operand(TableScan.class, ...))))
{code}
 

Then there is a RelNode tree like this:
{code:java}
MultiJoin
  Project
    Filter  -- matched unsuccessfully and jump out
      TableScan
  Project
    TableScan -- matched successfully{code}
 

And HepPlanner adds temporary nodes into 'bindings' list when applying a rule 
and trying to match operands. When fail to match a subtree, HepPlanner would 
jump out immediately and try other possible subtrees, but DO NOT pop the last 
added node:

 
{code:java}
private static boolean matchOperands(
    RelOptRuleOperand operand,
    RelNode rel,
    List<RelNode> bindings,                           // To store temporary 
matched nodes when applying a rule
    Map<RelNode, List<RelNode>> nodeChildren) {
  if (!operand.matches(rel)) {
    return false;
  }
  for (RelNode input : rel.getInputs()) {
    if (!(input instanceof HepRelVertex)) {
      // The graph could be partially optimized for materialized view. In that
      // case, the input would be a RelNode and shouldn't be matched again here.
      return false;
    }
  }
  bindings.add(rel);                            // Add a matched nodes when 
applying a rule
  @SuppressWarnings("unchecked")
  List<HepRelVertex> childRels = (List) rel.getInputs();
  switch (operand.childPolicy) {
  case ANY:
    return true;
  case UNORDERED:
    // For each operand, at least one child must match. If
    // matchAnyChildren, usually there's just one operand.
    for (RelOptRuleOperand childOperand : operand.getChildOperands()) {
      boolean match = false;
      for (HepRelVertex childRel : childRels) {
        match =
            matchOperands(
                childOperand,
                childRel.getCurrentRel(),
                bindings,
                nodeChildren);
        if (match) {
          break;
        }
      }
      if (!match) {
        return false;                // If failed to match, it just returns 
false but not pops the last item added in the bindings 
      }
    }
    final List<RelNode> children = new ArrayList<>(childRels.size());
    for (HepRelVertex childRel : childRels) {
      children.add(childRel.getCurrentRel());
    }
    nodeChildren.put(rel, children);
    return true;
  default:
    int n = operand.getChildOperands().size();
    if (childRels.size() < n) {
      return false;
    }
    for (Pair<HepRelVertex, RelOptRuleOperand> pair
        : Pair.zip(childRels, operand.getChildOperands())) {
      boolean match =
          matchOperands(
              pair.right,
              pair.left.getCurrentRel(),
              bindings,
              nodeChildren);
      if (!match) {
        return false;
      }
    }
    return true;
  }
}
{code}
 

 

If HepPlanner matches ANOTHER subtree for current rule finally, it will 
constructs a HepRuleCall using bindings as the matched relnodes, which contains 
unnecessary nodes yet, and the assertion fails.
{code:java}
......


boolean match =
    matchOperands(
        rule.getOperand(),
        vertex.getCurrentRel(),
        bindings,
        nodeChildren);

if (!match) {
  return null;
}

HepRuleCall call =
    new HepRuleCall(
        this,
        rule.getOperand(),
        bindings.toArray(new RelNode[0]),      // Use the bindings, which 
contains unnecessary relnodes, to construct a HepRuleCall
        nodeChildren,
        parents);

// Allow the rule to apply its own side-conditions.
if (!rule.matches(call)) {
  return null;
}

fireRule(call);

......{code}
And an assertion in constructor fails  
{code:java}
/**
 * Creates a RelOptRuleCall.
 *
 * @param planner      Planner
 * @param operand      Root operand
 * @param rels         Array of relational expressions which matched each
 *                     operand
 * @param nodeInputs   For each node which matched with
 *                     {@code matchAnyChildren}=true, a list of the node's
 *                     inputs
 * @param parents      list of parent RelNodes corresponding to the first
 *                     relational expression in the array argument, if known;
 *                     otherwise, null
 */
protected RelOptRuleCall(
    RelOptPlanner planner,
    RelOptRuleOperand operand,
    RelNode[] rels,
    Map<RelNode, List<RelNode>> nodeInputs,
    @Nullable List<RelNode> parents) {
  this.id = nextId++;
  this.planner = planner;
  this.operand0 = operand;
  this.nodeInputs = nodeInputs;
  this.rule = operand.getRule();
  this.rels = rels;
  this.parents = parents;

// Assertion fails because of the length of rels(the same as bindings) is 
greater than the length of rules.operands
   assert rels.length == rule.operands.size();    
}
 {code}
 

  was:
First I define a pattern(For demonstration)  like this:
{code:java}
// pattern like this, using 'unordered' child policy for MultiJoin
operandJ(MultiJoin.class, ...
  unordered(
    operand(Project.class, ...
      operand(TableScan.class, ...))))
{code}
Then there is a RelNode tree like this:
{code:java}
MultiJoin
  Project
    Filter  -- matched unsuccessfully and jump out
      TableScan
  Project
    TableScan -- matched successfully{code}
and HepPlanner adds temporary nodes into 'bindings' list when applying a rule 
and trying to match operands:

!image-2022-09-01-19-34-04-600.png!  

When fail to match a subtree, HepPlanner would jump out immediately and try 
other possible subtrees, but DO NOT pop the last added node:

!image-2022-09-01-19-36-29-325.png!

 

If HepPlanner matches a subtree for current rule finally, it will constructs a 
HepRuleCall using bindings as the matched relnodes, which contains unnecessary 
nodes yet

!image-2022-09-01-19-41-48-852.png!

And an assertion in constructor fails  

!image-2022-09-01-19-42-08-804.png!

 


> The bindings should properly pop last added RelNode when fail to match a 
> subtree
> --------------------------------------------------------------------------------
>
>                 Key: CALCITE-5260
>                 URL: https://issues.apache.org/jira/browse/CALCITE-5260
>             Project: Calcite
>          Issue Type: Bug
>          Components: core
>    Affects Versions: 1.31.0
>            Reporter: mengdou
>            Assignee: mengdou
>            Priority: Minor
>         Attachments: image-2022-09-01-19-34-04-600.png, 
> image-2022-09-01-19-36-29-325.png, image-2022-09-01-19-41-48-852.png, 
> image-2022-09-01-19-42-08-804.png
>
>
> First I define a pattern(For demonstration)  like this:
> {code:java}
> // pattern like this, using 'unordered' child policy for MultiJoin
> operandJ(MultiJoin.class, ...
>   unordered(
>     operand(Project.class, ...
>       operand(TableScan.class, ...))))
> {code}
>  
> Then there is a RelNode tree like this:
> {code:java}
> MultiJoin
>   Project
>     Filter  -- matched unsuccessfully and jump out
>       TableScan
>   Project
>     TableScan -- matched successfully{code}
>  
> And HepPlanner adds temporary nodes into 'bindings' list when applying a rule 
> and trying to match operands. When fail to match a subtree, HepPlanner would 
> jump out immediately and try other possible subtrees, but DO NOT pop the last 
> added node:
>  
> {code:java}
> private static boolean matchOperands(
>     RelOptRuleOperand operand,
>     RelNode rel,
>     List<RelNode> bindings,                           // To store temporary 
> matched nodes when applying a rule
>     Map<RelNode, List<RelNode>> nodeChildren) {
>   if (!operand.matches(rel)) {
>     return false;
>   }
>   for (RelNode input : rel.getInputs()) {
>     if (!(input instanceof HepRelVertex)) {
>       // The graph could be partially optimized for materialized view. In that
>       // case, the input would be a RelNode and shouldn't be matched again 
> here.
>       return false;
>     }
>   }
>   bindings.add(rel);                            // Add a matched nodes when 
> applying a rule
>   @SuppressWarnings("unchecked")
>   List<HepRelVertex> childRels = (List) rel.getInputs();
>   switch (operand.childPolicy) {
>   case ANY:
>     return true;
>   case UNORDERED:
>     // For each operand, at least one child must match. If
>     // matchAnyChildren, usually there's just one operand.
>     for (RelOptRuleOperand childOperand : operand.getChildOperands()) {
>       boolean match = false;
>       for (HepRelVertex childRel : childRels) {
>         match =
>             matchOperands(
>                 childOperand,
>                 childRel.getCurrentRel(),
>                 bindings,
>                 nodeChildren);
>         if (match) {
>           break;
>         }
>       }
>       if (!match) {
>         return false;                // If failed to match, it just returns 
> false but not pops the last item added in the bindings 
>       }
>     }
>     final List<RelNode> children = new ArrayList<>(childRels.size());
>     for (HepRelVertex childRel : childRels) {
>       children.add(childRel.getCurrentRel());
>     }
>     nodeChildren.put(rel, children);
>     return true;
>   default:
>     int n = operand.getChildOperands().size();
>     if (childRels.size() < n) {
>       return false;
>     }
>     for (Pair<HepRelVertex, RelOptRuleOperand> pair
>         : Pair.zip(childRels, operand.getChildOperands())) {
>       boolean match =
>           matchOperands(
>               pair.right,
>               pair.left.getCurrentRel(),
>               bindings,
>               nodeChildren);
>       if (!match) {
>         return false;
>       }
>     }
>     return true;
>   }
> }
> {code}
>  
>  
> If HepPlanner matches ANOTHER subtree for current rule finally, it will 
> constructs a HepRuleCall using bindings as the matched relnodes, which 
> contains unnecessary nodes yet, and the assertion fails.
> {code:java}
> ......
> boolean match =
>     matchOperands(
>         rule.getOperand(),
>         vertex.getCurrentRel(),
>         bindings,
>         nodeChildren);
> if (!match) {
>   return null;
> }
> HepRuleCall call =
>     new HepRuleCall(
>         this,
>         rule.getOperand(),
>         bindings.toArray(new RelNode[0]),      // Use the bindings, which 
> contains unnecessary relnodes, to construct a HepRuleCall
>         nodeChildren,
>         parents);
> // Allow the rule to apply its own side-conditions.
> if (!rule.matches(call)) {
>   return null;
> }
> fireRule(call);
> ......{code}
> And an assertion in constructor fails  
> {code:java}
> /**
>  * Creates a RelOptRuleCall.
>  *
>  * @param planner      Planner
>  * @param operand      Root operand
>  * @param rels         Array of relational expressions which matched each
>  *                     operand
>  * @param nodeInputs   For each node which matched with
>  *                     {@code matchAnyChildren}=true, a list of the node's
>  *                     inputs
>  * @param parents      list of parent RelNodes corresponding to the first
>  *                     relational expression in the array argument, if known;
>  *                     otherwise, null
>  */
> protected RelOptRuleCall(
>     RelOptPlanner planner,
>     RelOptRuleOperand operand,
>     RelNode[] rels,
>     Map<RelNode, List<RelNode>> nodeInputs,
>     @Nullable List<RelNode> parents) {
>   this.id = nextId++;
>   this.planner = planner;
>   this.operand0 = operand;
>   this.nodeInputs = nodeInputs;
>   this.rule = operand.getRule();
>   this.rels = rels;
>   this.parents = parents;
> // Assertion fails because of the length of rels(the same as bindings) is 
> greater than the length of rules.operands
>    assert rels.length == rule.operands.size();    
> }
>  {code}
>  



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to