[ 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)