[fix](planner) the project expr should be calculated in join node in some case (#17035)

Consider the sql bellow:

select sum(cc.qlnm) as qlnm
FROM
  outerjoin_A
  left join (SELECT
      outerjoin_B.b,
      coalesce(outerjoin_C.c, 0) AS qlnm
    FROM
      outerjoin_B
      inner JOIN outerjoin_C ON outerjoin_B.b = outerjoin_C.c
  ) cc on outerjoin_A.a = cc.b
group by outerjoin_A.a;

The coalesce(outerjoin_C.c, 0) was calculated in the agg node, which is wrong.
This pr correct this, and the expr is calculated in the inner join node now.
This commit is contained in:
starocean999
2023-02-24 15:20:05 +08:00
committed by GitHub
parent 7470198df6
commit 479d57df88
7 changed files with 138 additions and 9 deletions

View File

@ -137,6 +137,10 @@ public final class ExprSubstitutionMap {
lhs = lhsExprList;
}
public void updateRhsExprs(List<Expr> rhsExprList) {
rhs = rhsExprList;
}
/**
* Return a map which is equivalent to applying f followed by g,
* i.e., g(f()).

View File

@ -586,4 +586,36 @@ public abstract class JoinNodeBase extends PlanNode {
public void setvSrcToOutputSMap(List<Expr> lhs) {
this.vSrcToOutputSMap = new ExprSubstitutionMap(lhs, Collections.emptyList());
}
public void setOutputSmap(ExprSubstitutionMap smap, Analyzer analyzer) {
outputSmap = smap;
ExprSubstitutionMap tmpSmap = new ExprSubstitutionMap(Lists.newArrayList(vSrcToOutputSMap.getRhs()),
Lists.newArrayList(vSrcToOutputSMap.getLhs()));
List<Expr> newRhs = Lists.newArrayList();
boolean bSmapChanged = false;
for (Expr expr : smap.getRhs()) {
if (expr instanceof SlotRef) {
newRhs.add(expr);
} else {
// we need do project in the join node
// add a new slot for projection result and add the project expr to vSrcToOutputSMap
SlotDescriptor slotDesc = analyzer.addSlotDescriptor(vOutputTupleDesc);
slotDesc.initFromExpr(expr);
slotDesc.setIsMaterialized(true);
// the project expr is from smap, which use the slots of hash join node's output tuple
// we need substitute it to make sure the project expr use slots from intermediate tuple
expr.substitute(tmpSmap);
vSrcToOutputSMap.getLhs().add(expr);
SlotRef slotRef = new SlotRef(slotDesc);
vSrcToOutputSMap.getRhs().add(slotRef);
newRhs.add(slotRef);
bSmapChanged = true;
}
}
if (bSmapChanged) {
outputSmap.updateRhsExprs(newRhs);
vOutputTupleDesc.computeStatAndMemLayout();
}
}
}

View File

@ -692,7 +692,7 @@ public abstract class PlanNode extends TreeNode<PlanNode> implements PlanStats {
return outputSmap;
}
public void setOutputSmap(ExprSubstitutionMap smap) {
public void setOutputSmap(ExprSubstitutionMap smap, Analyzer analyzer) {
outputSmap = smap;
}

View File

@ -204,7 +204,7 @@ public class SingleNodePlanner {
// Set the output smap to resolve exprs referencing inline views within stmt.
// Not needed for a UnionStmt because it materializes its input operands.
if (stmt instanceof SelectStmt) {
node.setOutputSmap(((SelectStmt) stmt).getBaseTblSmap());
node.setOutputSmap(((SelectStmt) stmt).getBaseTblSmap(), analyzer);
}
return node;
}
@ -320,7 +320,7 @@ public class SingleNodePlanner {
scanNodes.removeIf(scanNode -> scanTupleIds.contains(scanNode.getTupleIds().get(0)));
PlanNode node = createEmptyNode(root, stmt, analyzer);
// Ensure result exprs will be substituted by right outputSmap
node.setOutputSmap(root.outputSmap);
node.setOutputSmap(root.outputSmap, analyzer);
return node;
}
@ -1149,7 +1149,7 @@ public class SingleNodePlanner {
}
final PlanNode emptySetNode = new EmptySetNode(ctx.getNextNodeId(), rowTuples);
emptySetNode.init(analyzer);
emptySetNode.setOutputSmap(selectStmt.getBaseTblSmap());
emptySetNode.setOutputSmap(selectStmt.getBaseTblSmap(), analyzer);
return createAggregationPlan(selectStmt, analyzer, emptySetNode);
}
@ -1588,7 +1588,7 @@ public class SingleNodePlanner {
// conjuncts into outer-joined inline views, so hasEmptyResultSet() cannot be
// true for an outer-joined inline view that has no table refs.
Preconditions.checkState(!analyzer.isOuterJoined(inlineViewRef.getId()));
emptySetNode.setOutputSmap(inlineViewRef.getSmap());
emptySetNode.setOutputSmap(inlineViewRef.getSmap(), analyzer);
return emptySetNode;
}
// Analysis should have generated a tuple id into which to materialize the exprs.
@ -1605,7 +1605,7 @@ public class SingleNodePlanner {
unionNode.init(analyzer);
//set outputSmap to substitute literal in outputExpr
unionNode.setWithoutTupleIsNullOutputSmap(inlineViewRef.getSmap());
unionNode.setOutputSmap(inlineViewRef.getSmap());
unionNode.setOutputSmap(inlineViewRef.getSmap(), analyzer);
if (analyzer.isOuterJoined(inlineViewRef.getId())) {
List<Expr> nullableRhs;
if (analyzer.isOuterJoinedLeftSide(inlineViewRef.getId())) {
@ -1615,7 +1615,8 @@ public class SingleNodePlanner {
nullableRhs = TupleIsNullPredicate.wrapExprs(inlineViewRef.getSmap().getRhs(),
unionNode.getTupleIds(), TNullSide.RIGHT, analyzer);
}
unionNode.setOutputSmap(new ExprSubstitutionMap(inlineViewRef.getSmap().getLhs(), nullableRhs));
unionNode.setOutputSmap(
new ExprSubstitutionMap(inlineViewRef.getSmap().getLhs(), nullableRhs), analyzer);
}
return unionNode;
}
@ -1646,7 +1647,7 @@ public class SingleNodePlanner {
outputSmap = new ExprSubstitutionMap(outputSmap.getLhs(), nullableRhs);
}
// Set output smap of rootNode *before* creating a SelectNode for proper resolution.
rootNode.setOutputSmap(outputSmap);
rootNode.setOutputSmap(outputSmap, analyzer);
if (rootNode instanceof UnionNode && ((UnionNode) rootNode).isConstantUnion()) {
rootNode.setWithoutTupleIsNullOutputSmap(outputSmap);
}