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