[
https://issues.apache.org/jira/browse/CALCITE-5260?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
mengdou updated CALCITE-5260:
-----------------------------
Affects Version/s: 1.32.0
(was: 1.31.0)
> 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.32.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)