diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownVectorTopNIntoOlapScan.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownVectorTopNIntoOlapScan.java index 271a043b48a83b..26750184a70838 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownVectorTopNIntoOlapScan.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownVectorTopNIntoOlapScan.java @@ -56,14 +56,14 @@ public List buildRules() { LogicalProject project = topN.child(); LogicalOlapScan scan = project.child(); return pushDown(topN, project, scan, Optional.empty()); - }).toRule(RuleType.PUSH_DOWN_VIRTUAL_COLUMNS_INTO_OLAP_SCAN), + }).toRule(RuleType.PUSH_DOWN_VECTOR_TOPN_INTO_OLAP_SCAN), logicalTopN(logicalProject(logicalFilter(logicalOlapScan()))) .when(t -> t.getOrderKeys().size() == 1).then(topN -> { LogicalProject> project = topN.child(); LogicalFilter filter = project.child(); LogicalOlapScan scan = filter.child(); return pushDown(topN, project, scan, Optional.of(filter)); - }).toRule(RuleType.PUSH_DOWN_VIRTUAL_COLUMNS_INTO_OLAP_SCAN) + }).toRule(RuleType.PUSH_DOWN_VECTOR_TOPN_INTO_OLAP_SCAN) ); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownVirtualColumnsIntoOlapScan.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownVirtualColumnsIntoOlapScan.java index 0f17f7910fad89..36a8ee3a6d9b3c 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownVirtualColumnsIntoOlapScan.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownVirtualColumnsIntoOlapScan.java @@ -32,6 +32,7 @@ import org.apache.doris.nereids.trees.expressions.WhenClause; import org.apache.doris.nereids.trees.expressions.functions.scalar.DecodeAsVarchar; import org.apache.doris.nereids.trees.expressions.functions.scalar.EncodeString; +import org.apache.doris.nereids.trees.expressions.functions.scalar.GroupingScalarFunction; import org.apache.doris.nereids.trees.expressions.functions.scalar.IsIpAddressInRange; import org.apache.doris.nereids.trees.expressions.functions.scalar.Lambda; import org.apache.doris.nereids.trees.expressions.functions.scalar.MultiMatch; @@ -267,9 +268,10 @@ private void extractRepeatedSubExpressions(LogicalFilter filter for (Expression expr : allExpressions) { // Skip expressions that contain lambda functions anywhere in the tree - if (expr.anyMatch(e -> e instanceof Lambda)) { + if (expr.anyMatch(e -> e instanceof Lambda) + || expr.anyMatch(e -> e instanceof GroupingScalarFunction)) { if (LOG.isDebugEnabled()) { - LOG.debug("Skipping expression containing lambda: {}", expr.toSql()); + LOG.debug("Skipping expression containing lambda/grouping: {}", expr.toSql()); } continue; } @@ -348,6 +350,11 @@ private void collectSubExpressions(Expression expr, Map exp * @return SkipResult indicating how to handle this expression */ private SkipResult shouldSkipExpression(Expression expr) { + // Grouping scalar functions can't be materialized into project/virtual columns. + // If an expression tree contains any grouping function, skip it entirely. + if (expr.anyMatch(e -> e instanceof GroupingScalarFunction)) { + return SkipResult.TERMINATE; + } // Skip simple slots and literals as they don't benefit from being pushed down if (expr instanceof Slot || expr.isConstant()) { return SkipResult.TERMINATE; diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PushDownVirtualColumnsIntoOlapScanTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PushDownVirtualColumnsIntoOlapScanTest.java index 1603252154d564..431cfb6ca22888 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PushDownVirtualColumnsIntoOlapScanTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PushDownVirtualColumnsIntoOlapScanTest.java @@ -39,6 +39,7 @@ import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayFilter; import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayMap; import org.apache.doris.nereids.trees.expressions.functions.scalar.Concat; +import org.apache.doris.nereids.trees.expressions.functions.scalar.Grouping; import org.apache.doris.nereids.trees.expressions.functions.scalar.IsIpAddressInRange; import org.apache.doris.nereids.trees.expressions.functions.scalar.L2Distance; import org.apache.doris.nereids.trees.expressions.functions.scalar.Lambda; @@ -832,6 +833,62 @@ public void testTypeFilteringWithMixedExpressions() { } } + @Test + public void testSkipGroupingFunctionsInFilter() { + // Ensure expressions containing grouping() are completely skipped + LogicalOlapScan scan = PlanConstructor.newLogicalOlapScan(0, "t1", 0); + SlotReference x = (SlotReference) scan.getOutput().get(0); + + Grouping grouping1 = new Grouping(x); + Grouping grouping2 = new Grouping(x); + // even if repeated, should not extract + LogicalFilter filter = new LogicalFilter<>( + ImmutableSet.of(new EqualTo(grouping1, grouping2)), scan); + PlanChecker.from(MemoTestUtils.createConnectContext(), filter) + .applyTopDown(new PushDownVirtualColumnsIntoOlapScan()) + .matches(logicalOlapScan().when(o -> o.getVirtualColumns().isEmpty())); + } + + @Test + public void testSkipGroupingFunctionsInProject() { + // Ensure grouping() in project is not extracted or altered by CSE + LogicalOlapScan scan = PlanConstructor.newLogicalOlapScan(0, "t1", 0); + SlotReference x = (SlotReference) scan.getOutput().get(0); + + Grouping grouping1 = new Grouping(x); + Grouping grouping2 = new Grouping(x); + + LogicalFilter filter = new LogicalFilter<>(ImmutableSet.of(), scan); + List projects = ImmutableList.of( + new Alias(grouping1, "g1"), + new Alias(grouping2, "g2"), + new Alias(x, "x") + ); + LogicalProject> project = new LogicalProject<>(projects, filter); + + Plan result = PlanChecker.from(MemoTestUtils.createConnectContext(), project) + .applyTopDown(new PushDownVirtualColumnsIntoOlapScan()) + .getPlan(); + + Assertions.assertInstanceOf(LogicalProject.class, result); + LogicalProject resProject = (LogicalProject) result; + Assertions.assertInstanceOf(LogicalFilter.class, resProject.child()); + LogicalFilter resFilter = (LogicalFilter) resProject.child(); + Assertions.assertInstanceOf(LogicalOlapScan.class, resFilter.child()); + LogicalOlapScan resScan = (LogicalOlapScan) resFilter.child(); + Assertions.assertTrue(resScan.getVirtualColumns().isEmpty(), + "Grouping in project must not trigger virtual column extraction"); + + // Ensure grouping aliases remain present + List aliasNames = resProject.getProjects().stream().map(ne -> { + if (ne instanceof Alias) { + return ((Alias) ne).getName(); + } + return ""; + }).collect(Collectors.toList()); + Assertions.assertTrue(aliasNames.contains("g1") && aliasNames.contains("g2")); + } + @Test void testOnceUniqueFunction() { LogicalOlapScan olapScan = new LogicalOlapScan(StatementScopeIdGenerator.newRelationId(),