[fix](nereids) AdjustNullable rule should handle union node with no children (#35074)

The output slot's nullable info is not correctly calculated in union node.
Because old code only get correct result if union node has children.
But the union node may have no children but only have constantExprList.
So in that case, we should calculate output's nullable info byboth children and constantExprList.
This commit is contained in:
starocean999
2024-05-24 14:33:12 +08:00
committed by yiguolei
parent f6beeb1ddd
commit bfe293c725
4 changed files with 61 additions and 27 deletions

View File

@ -165,45 +165,55 @@ public class AdjustNullable extends DefaultPlanRewriter<Map<ExprId, Slot>> imple
@Override
public Plan visitLogicalSetOperation(LogicalSetOperation setOperation, Map<ExprId, Slot> replaceMap) {
setOperation = (LogicalSetOperation) super.visit(setOperation, replaceMap);
if (setOperation.children().isEmpty()) {
return setOperation;
}
List<Boolean> inputNullable = setOperation.child(0).getOutput().stream()
.map(ExpressionTrait::nullable).collect(Collectors.toList());
ImmutableList.Builder<List<SlotReference>> newChildrenOutputs = ImmutableList.builder();
for (int i = 0; i < setOperation.arity(); i++) {
List<Slot> childOutput = setOperation.child(i).getOutput();
List<SlotReference> setChildOutput = setOperation.getRegularChildOutput(i);
ImmutableList.Builder<SlotReference> newChildOutputs = ImmutableList.builder();
for (int j = 0; j < setChildOutput.size(); j++) {
for (Slot slot : childOutput) {
if (slot.getExprId().equals(setChildOutput.get(j).getExprId())) {
inputNullable.set(j, slot.nullable() || inputNullable.get(j));
newChildOutputs.add((SlotReference) slot);
break;
List<Boolean> inputNullable = null;
if (!setOperation.children().isEmpty()) {
inputNullable = setOperation.child(0).getOutput().stream()
.map(ExpressionTrait::nullable).collect(Collectors.toList());
for (int i = 0; i < setOperation.arity(); i++) {
List<Slot> childOutput = setOperation.child(i).getOutput();
List<SlotReference> setChildOutput = setOperation.getRegularChildOutput(i);
ImmutableList.Builder<SlotReference> newChildOutputs = ImmutableList.builder();
for (int j = 0; j < setChildOutput.size(); j++) {
for (Slot slot : childOutput) {
if (slot.getExprId().equals(setChildOutput.get(j).getExprId())) {
inputNullable.set(j, slot.nullable() || inputNullable.get(j));
newChildOutputs.add((SlotReference) slot);
break;
}
}
}
newChildrenOutputs.add(newChildOutputs.build());
}
newChildrenOutputs.add(newChildOutputs.build());
}
if (setOperation instanceof LogicalUnion) {
LogicalUnion logicalUnion = (LogicalUnion) setOperation;
for (List<NamedExpression> constantExprs : logicalUnion.getConstantExprsList()) {
for (int j = 0; j < constantExprs.size(); j++) {
if (constantExprs.get(j).nullable()) {
inputNullable.set(j, true);
}
if (!logicalUnion.getConstantExprsList().isEmpty() && setOperation.children().isEmpty()) {
int outputSize = logicalUnion.getConstantExprsList().get(0).size();
// create the inputNullable list and fill it with all FALSE values
inputNullable = Lists.newArrayListWithCapacity(outputSize);
for (int i = 0; i < outputSize; i++) {
inputNullable.add(false);
}
}
for (List<NamedExpression> constantExprs : logicalUnion.getConstantExprsList()) {
for (int j = 0; j < constantExprs.size(); j++) {
inputNullable.set(j, inputNullable.get(j) || constantExprs.get(j).nullable());
}
}
}
if (inputNullable == null) {
// this is a fail-safe
// means there is no children and having no getConstantExprsList
// no way to update the nullable flag, so just do nothing
return setOperation;
}
List<NamedExpression> outputs = setOperation.getOutputs();
List<NamedExpression> newOutputs = Lists.newArrayListWithCapacity(outputs.size());
for (int i = 0; i < inputNullable.size(); i++) {
NamedExpression ne = outputs.get(i);
Slot slot = ne instanceof Alias ? (Slot) ((Alias) ne).child() : (Slot) ne;
if (inputNullable.get(i)) {
slot = slot.withNullable(true);
}
slot = slot.withNullable(inputNullable.get(i));
newOutputs.add(ne instanceof Alias ? (NamedExpression) ne.withChildren(slot) : slot);
}
newOutputs.forEach(o -> replaceMap.put(o.getExprId(), o.toSlot()));

View File

@ -67,9 +67,8 @@ public class PushProjectIntoUnion extends OneRewriteRuleFactory {
}
newConstExprs.add(newProjections.build());
}
return p.child()
.withChildrenAndConstExprsList(ImmutableList.of(), ImmutableList.of(), newConstExprs.build())
.withNewOutputs(p.getOutputs());
return p.child().withNewOutputsChildrenAndConstExprsList(ImmutableList.copyOf(p.getOutput()),
ImmutableList.of(), ImmutableList.of(), newConstExprs.build());
}).toRule(RuleType.PUSH_PROJECT_INTO_UNION);
}
}

View File

@ -180,6 +180,13 @@ public class LogicalUnion extends LogicalSetOperation implements Union, OutputPr
return new LogicalUnion(qualifier, outputs, childrenOutputs, constantExprsList, hasPushedFilter, children);
}
public LogicalUnion withNewOutputsChildrenAndConstExprsList(List<NamedExpression> newOutputs, List<Plan> children,
List<List<SlotReference>> childrenOutputs,
List<List<NamedExpression>> constantExprsList) {
return new LogicalUnion(qualifier, newOutputs, childrenOutputs, constantExprsList,
hasPushedFilter, Optional.empty(), Optional.empty(), children);
}
public LogicalUnion withAllQualifier() {
return new LogicalUnion(Qualifier.ALL, outputs, regularChildrenOutputs, constantExprsList, hasPushedFilter,
Optional.empty(), Optional.empty(), children);

View File

@ -95,5 +95,23 @@ suite("adjust_nullable") {
sql """
drop table if exists table_8_undef_undef;
"""
sql """
drop table if exists orders_2_x;
"""
sql """CREATE TABLE `orders_2_x` (
`o_orderdate` DATE not NULL
) ENGINE=OLAP
DUPLICATE KEY(`o_orderdate`)
DISTRIBUTED BY HASH(`o_orderdate`) BUCKETS 96
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);"""
explain {
sql("verbose insert into orders_2_x values ( '2023-10-17'),( '2023-10-17');")
notContains("nullable=true")
}
}