Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import org.apache.doris.nereids.rules.analysis.NormalizeAggregate;
import org.apache.doris.nereids.rules.expression.rules.FoldConstantRuleOnFE;
import org.apache.doris.nereids.trees.expressions.Alias;
import org.apache.doris.nereids.trees.expressions.Cast;
import org.apache.doris.nereids.trees.expressions.ExprId;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.IsNull;
Expand Down Expand Up @@ -571,7 +570,6 @@ private LogicalAggregate<? extends Plan> storageLayerAggregate(

boolean containsCount = false;
boolean containsCountStar = false;
boolean countHasCastArgument = false;
Set<SlotReference> checkNullSlots = new HashSet<>();
Set<Expression> expressionAfterProject = new HashSet<>();

Expand All @@ -594,13 +592,6 @@ private LogicalAggregate<? extends Plan> storageLayerAggregate(
if (arg0 instanceof SlotReference) {
checkNullSlots.add((SlotReference) arg0);
expressionAfterProject.add(arg0);
} else if (arg0 instanceof Cast) {
countHasCastArgument = true;
Expression child0 = arg0.child(0);
if (child0 instanceof SlotReference) {
checkNullSlots.add((SlotReference) child0);
expressionAfterProject.add(arg0);
}
}
}
}
Expand All @@ -622,22 +613,13 @@ private LogicalAggregate<? extends Plan> storageLayerAggregate(
}
}

// TODO: refactor this to process slot reference or expression together
boolean onlyContainsSlotOrNumericCastSlot = aggregateFunctions.stream()
// Storage-layer aggregation operates on source column values. A cast can change both
// values and nullability, so it must remain in the row-evaluation path above the scan.
boolean onlyContainsSlot = aggregateFunctions.stream()
.map(ExpressionTrait::getArguments)
.flatMap(List::stream)
.allMatch(argument -> {
if (argument instanceof SlotReference) {
return true;
}
if (argument instanceof Cast) {
return argument.child(0) instanceof SlotReference
&& argument.getDataType().isNumericType()
&& argument.child(0).getDataType().isNumericType();
}
return false;
});
if (!onlyContainsSlotOrNumericCastSlot) {
.allMatch(SlotReference.class::isInstance);
if (!onlyContainsSlot) {
return canNotPush;
}

Expand All @@ -664,35 +646,13 @@ private LogicalAggregate<? extends Plan> storageLayerAggregate(
if (needCheckSlotNull) {
checkNullSlots.add((SlotReference) argument);
}
} else if (argument instanceof Cast) {
boolean castMatch = argument.child(0) instanceof SlotReference
&& argument.getDataType().isNumericType()
&& argument.child(0).getDataType().isNumericType();
if (!castMatch) {
return canNotPush;
} else {
if (needCheckSlotNull) {
countHasCastArgument = true;
checkNullSlots.add((SlotReference) argument.child(0));
}
}
} else {
return canNotPush;
}
}
argumentsOfAggregateFunction = processedExpressions;
}

// File aggregate metadata can describe COUNT(*) or COUNT(file_column), but it cannot
// describe the CAST wrapped around a COUNT argument. Dropping that CAST is incorrect even
// when the source column is NOT NULL. For example, a non-null DOUBLE value outside the INT
// range becomes NULL for CAST(double_col AS INT), so COUNT(CAST(double_col AS INT)) must
// exclude it while a footer-level COUNT(double_col) would include it. Keep OLAP's existing
// storage-layer behavior unchanged, and make external files evaluate the CAST normally.
if (logicalScan instanceof LogicalFileScan && countHasCastArgument) {
return canNotPush;
}

Set<PushDownAggOp> pushDownAggOps = functionClasses.stream()
.map(supportedAgg::get)
.collect(Collectors.toSet());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
import org.apache.doris.nereids.rules.RuleType;
import org.apache.doris.nereids.rules.implementation.AggregateStrategies;
import org.apache.doris.nereids.trees.expressions.Alias;
import org.apache.doris.nereids.trees.expressions.Cast;
import org.apache.doris.nereids.trees.expressions.TryCast;
import org.apache.doris.nereids.trees.expressions.functions.agg.AggregateFunction;
import org.apache.doris.nereids.trees.expressions.functions.agg.Count;
import org.apache.doris.nereids.trees.expressions.functions.agg.Max;
import org.apache.doris.nereids.trees.expressions.functions.agg.Min;
Expand All @@ -40,6 +43,7 @@
import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
import org.apache.doris.nereids.trees.plans.physical.PhysicalStorageLayerAggregate.PushDownAggOp;
import org.apache.doris.nereids.types.TinyIntType;
import org.apache.doris.nereids.util.MemoPatternMatchSupported;
import org.apache.doris.nereids.util.MemoTestUtils;
import org.apache.doris.nereids.util.PlanChecker;
Expand All @@ -50,6 +54,7 @@
import org.mockito.Mockito;

import java.util.Collections;
import java.util.List;
import java.util.Optional;

public class PhysicalStorageLayerAggregateTest implements MemoPatternMatchSupported {
Expand Down Expand Up @@ -259,6 +264,42 @@ public void testWithProject() {
);
}

@Test
public void testCastAggregateDoesNotUseStorageLayerAggregate() {
LogicalOlapScan olapScan = PlanConstructor.newLogicalOlapScan(2, "cast_aggregate", 0);
Cast cast = new Cast(olapScan.getOutput().get(0), TinyIntType.INSTANCE);
TryCast tryCast = new TryCast(olapScan.getOutput().get(0), TinyIntType.INSTANCE);
List<AggregateFunction> castAggregates = ImmutableList.of(
new Count(cast), new Count(tryCast), new Min(cast), new Max(tryCast));

for (AggregateFunction function : castAggregates) {
LogicalAggregate<LogicalOlapScan> aggregate = new LogicalAggregate<>(
Collections.emptyList(), ImmutableList.of(new Alias(function, "aggregate")),
true, Optional.empty(), olapScan);

PlanChecker.from(MemoTestUtils.createCascadesContext(aggregate))
.applyImplementation(storageLayerAggregateWithoutProject())
.matches(logicalAggregate(logicalOlapScan()));
}
}

@Test
public void testProjectedCastCountDoesNotUseStorageLayerAggregate() {
LogicalOlapScan olapScan = PlanConstructor.newLogicalOlapScan(3, "projected_cast_count", 0);
LogicalProject<LogicalOlapScan> project = new LogicalProject<>(
ImmutableList.of(new Alias(
new Cast(olapScan.getOutput().get(0), TinyIntType.INSTANCE), "cast_value")),
olapScan);
LogicalAggregate<LogicalProject<LogicalOlapScan>> aggregate = new LogicalAggregate<>(
Collections.emptyList(),
ImmutableList.of(new Alias(new Count(project.getOutput().get(0)), "count")),
true, Optional.empty(), project);

PlanChecker.from(MemoTestUtils.createCascadesContext(aggregate))
.applyImplementation(storageLayerAggregateWithProject())
.matches(logicalAggregate(logicalProject(logicalOlapScan())));
}

@Test
void testProjectionCheck() {
LogicalOlapScan olapScan = PlanConstructor.newLogicalOlapScan(1, "tbl", 0);
Expand Down
Loading