This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch branch-4.0
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.0 by this push:
new 0aae8caf0c9 branch-4.0: [fix](set operation) Use regular child output
for set operation rules #64908 (#65070)
0aae8caf0c9 is described below
commit 0aae8caf0c907b8bfa7a0b9969fc2de4a11ddf32
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu Jul 2 11:51:26 2026 +0800
branch-4.0: [fix](set operation) Use regular child output for set operation
rules #64908 (#65070)
Cherry-picked from #64908
Co-authored-by: morrySnow <[email protected]>
---
.../rewrite/MergeOneRowRelationIntoUnion.java | 21 ++-
.../rewrite/PushDownLimitDistinctThroughUnion.java | 7 +-
.../rewrite/PushDownTopNDistinctThroughUnion.java | 6 +-
.../rewrite/SetOperationOutputMappingTest.java | 157 +++++++++++++++++++++
.../merge_one_row_relation_into_union.groovy | 31 ++++
.../push_down_limit_distinct_through_union.groovy | 58 ++++++++
.../push_down_top_n_distinct_through_union.groovy | 21 ++-
7 files changed, 283 insertions(+), 18 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeOneRowRelationIntoUnion.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeOneRowRelationIntoUnion.java
index abda37ff0b4..9be1f0a2d83 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeOneRowRelationIntoUnion.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeOneRowRelationIntoUnion.java
@@ -19,19 +19,20 @@ package org.apache.doris.nereids.rules.rewrite;
import org.apache.doris.nereids.rules.Rule;
import org.apache.doris.nereids.rules.RuleType;
+import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.NamedExpression;
import org.apache.doris.nereids.trees.expressions.SlotReference;
import org.apache.doris.nereids.trees.plans.Plan;
import org.apache.doris.nereids.trees.plans.logical.LogicalOneRowRelation;
-import org.apache.doris.nereids.types.DataType;
import org.apache.doris.nereids.util.ExpressionUtils;
-import org.apache.doris.nereids.util.TypeCoercionUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.Lists;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* merge one row relation into union, for easy to compute physical properties
@@ -56,15 +57,13 @@ public class MergeOneRowRelationIntoUnion extends
OneRewriteRuleFactory {
} else {
ImmutableList.Builder<NamedExpression>
constantExprs = new Builder<>();
List<NamedExpression> projects =
((LogicalOneRowRelation) child).getProjects();
- for (int j = 0; j < projects.size(); j++) {
- NamedExpression project = projects.get(j);
- DataType targetType =
u.getOutput().get(j).getDataType();
- if (project.getDataType().equals(targetType)) {
- constantExprs.add(project);
- } else {
- constantExprs.add((NamedExpression)
project.withChildren(
-
TypeCoercionUtils.castIfNotSameType(project.child(0), targetType)));
- }
+ Map<Expression, Expression> replaceMap = new
HashMap<>();
+ for (NamedExpression project : projects) {
+ replaceMap.put(project.toSlot(), project);
+ }
+ for (Expression regularChildOutput :
u.getRegularChildOutput(i)) {
+ constantExprs.add((NamedExpression)
ExpressionUtils.replace(
+ regularChildOutput, replaceMap));
}
constantExprsList.add(constantExprs.build());
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownLimitDistinctThroughUnion.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownLimitDistinctThroughUnion.java
index df3069105d2..1a136673a1d 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownLimitDistinctThroughUnion.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownLimitDistinctThroughUnion.java
@@ -68,11 +68,11 @@ public class PushDownLimitDistinctThroughUnion implements
RewriteRuleFactory {
LogicalUnion union = agg.child();
List<Plan> newChildren = new ArrayList<>();
- for (Plan child : union.children()) {
+ for (int childIdx = 0; childIdx < union.arity();
++childIdx) {
Map<Expression, Expression> replaceMap = new
HashMap<>();
for (int i = 0; i < union.getOutputs().size();
++i) {
NamedExpression output =
union.getOutputs().get(i);
- replaceMap.put(output,
child.getOutput().get(i));
+ replaceMap.put(output,
union.getRegularChildOutput(childIdx).get(i));
}
List<Expression> newGroupBy =
agg.getGroupByExpressions().stream()
@@ -82,7 +82,8 @@ public class PushDownLimitDistinctThroughUnion implements
RewriteRuleFactory {
.map(expr ->
ExpressionUtils.replaceNameExpression(expr, replaceMap))
.collect(Collectors.toList());
- LogicalAggregate<Plan> newAgg = new
LogicalAggregate<>(newGroupBy, newOutputs, child);
+ LogicalAggregate<Plan> newAgg = new
LogicalAggregate<>(
+ newGroupBy, newOutputs,
union.child(childIdx));
LogicalLimit<Plan> newLimit =
limit.withLimitChild(limit.getLimit() + limit.getOffset(),
0, newAgg);
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNDistinctThroughUnion.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNDistinctThroughUnion.java
index f6e944a7a91..cde42c7ad24 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNDistinctThroughUnion.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNDistinctThroughUnion.java
@@ -68,18 +68,18 @@ public class PushDownTopNDistinctThroughUnion implements
RewriteRuleFactory {
LogicalAggregate<LogicalUnion> agg = topN.child();
LogicalUnion union = agg.child();
List<Plan> newChildren = new ArrayList<>();
- for (Plan child : union.children()) {
+ for (int childIdx = 0; childIdx < union.arity();
++childIdx) {
Map<Expression, Expression> replaceMap = new
HashMap<>();
for (int i = 0; i < union.getOutputs().size();
++i) {
NamedExpression output =
union.getOutputs().get(i);
- replaceMap.put(output,
child.getOutput().get(i));
+ replaceMap.put(output,
union.getRegularChildOutput(childIdx).get(i));
}
List<OrderKey> orderKeys =
topN.getOrderKeys().stream()
.map(orderKey ->
orderKey.withExpression(
ExpressionUtils.replace(orderKey.getExpr(), replaceMap)))
.collect(ImmutableList.toImmutableList());
newChildren.add(new LogicalTopN<>(orderKeys,
topN.getLimit() + topN.getOffset(), 0,
- PlanUtils.distinct(child)));
+
PlanUtils.distinct(union.child(childIdx))));
}
if (union.children().equals(newChildren)) {
return null;
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/SetOperationOutputMappingTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/SetOperationOutputMappingTest.java
new file mode 100644
index 00000000000..be2abce9128
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/SetOperationOutputMappingTest.java
@@ -0,0 +1,157 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.nereids.rules.rewrite;
+
+import org.apache.doris.nereids.trees.expressions.Alias;
+import org.apache.doris.nereids.trees.expressions.ExprId;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.StatementScopeIdGenerator;
+import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.RelationId;
+import org.apache.doris.nereids.trees.plans.algebra.SetOperation.Qualifier;
+import org.apache.doris.nereids.trees.plans.logical.LogicalOneRowRelation;
+import org.apache.doris.nereids.trees.plans.logical.LogicalUnion;
+import org.apache.doris.nereids.types.IntegerType;
+import org.apache.doris.nereids.util.MemoPatternMatchSupported;
+import org.apache.doris.nereids.util.MemoTestUtils;
+import org.apache.doris.nereids.util.PlanChecker;
+import org.apache.doris.utframe.TestWithFeService;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+class SetOperationOutputMappingTest extends TestWithFeService implements
MemoPatternMatchSupported {
+
+ @Override
+ protected void runBeforeAll() throws Exception {
+ createDatabase("test");
+ useDatabase("test");
+
connectContext.getSessionVariable().setDisableNereidsRules("PRUNE_EMPTY_PARTITION");
+
+ createTable("CREATE TABLE set_operation_output_mapping_t (\n"
+ + " k int NULL,\n"
+ + " v int NULL\n"
+ + ") ENGINE=OLAP\n"
+ + "DISTRIBUTED BY HASH(k) BUCKETS 1\n"
+ + "PROPERTIES (\n"
+ + " \"replication_allocation\" = \"tag.location.default:
1\"\n"
+ + ");");
+ }
+
+ @Override
+ protected void runBeforeEach() throws Exception {
+ StatementScopeIdGenerator.clear();
+ }
+
+ @Test
+ void testMergeOneRowRelationUsesRegularChildOutput() {
+ Alias firstProject = new Alias(new ExprId(1), new IntegerLiteral(10),
"first_col");
+ Alias secondProject = new Alias(new ExprId(2), new IntegerLiteral(20),
"second_col");
+ SlotReference secondProjectSlot = (SlotReference)
secondProject.toSlot();
+ LogicalOneRowRelation oneRowRelation = new LogicalOneRowRelation(
+ new RelationId(1), ImmutableList.of(firstProject,
secondProject));
+
+ SlotReference unionOutput = new SlotReference(new ExprId(10),
"second_col",
+ IntegerType.INSTANCE, false, ImmutableList.of());
+ LogicalUnion union = new LogicalUnion(Qualifier.ALL,
+ ImmutableList.of(unionOutput),
+ ImmutableList.of(ImmutableList.of(secondProjectSlot)),
+ ImmutableList.of(),
+ false,
+ ImmutableList.of(oneRowRelation));
+
+ Plan rewritten =
PlanChecker.from(MemoTestUtils.createConnectContext(), union)
+ .applyTopDown(new MergeOneRowRelationIntoUnion())
+ .getPlan();
+
+ Assertions.assertInstanceOf(LogicalUnion.class, rewritten);
+ LogicalUnion rewrittenUnion = (LogicalUnion) rewritten;
+ Assertions.assertEquals(0, rewrittenUnion.children().size());
+ Assertions.assertEquals(1,
rewrittenUnion.getConstantExprsList().size());
+
+ List<NamedExpression> constantExprs =
rewrittenUnion.getConstantExprsList().get(0);
+ Assertions.assertEquals(1, constantExprs.size());
+ Assertions.assertEquals(secondProject.getExprId(),
constantExprs.get(0).getExprId());
+ Assertions.assertInstanceOf(IntegerLiteral.class,
constantExprs.get(0).child(0));
+ Assertions.assertEquals(20, ((IntegerLiteral)
constantExprs.get(0).child(0)).getValue());
+ }
+
+ @Test
+ void testPushDownTopNDistinctThroughUnionUsesRegularChildOutput() {
+ String sql = "SELECT *\n"
+ + "FROM (\n"
+ + " SELECT *\n"
+ + " FROM (\n"
+ + " SELECT k, v, v, ROW_NUMBER() OVER (ORDER BY k DESC)\n"
+ + " FROM set_operation_output_mapping_t\n"
+ + " ) u1\n"
+ + " UNION\n"
+ + " SELECT *\n"
+ + " FROM (\n"
+ + " SELECT k, v, v, ROW_NUMBER() OVER (ORDER BY k DESC)\n"
+ + " FROM set_operation_output_mapping_t\n"
+ + " ) u2\n"
+ + ") u\n"
+ + "ORDER BY 1\n"
+ + "LIMIT 10";
+
+ PlanChecker.from(connectContext)
+ .analyze(sql)
+ .rewrite()
+ .matches(
+ logicalUnion(
+ logicalTopN().when(topN -> topN.getLimit() ==
10 && topN.getOffset() == 0),
+ logicalTopN().when(topN -> topN.getLimit() ==
10 && topN.getOffset() == 0)
+ )
+ );
+ }
+
+ @Test
+ void testPushDownLimitDistinctThroughUnionUsesRegularChildOutput() {
+ String sql = "SELECT DISTINCT *\n"
+ + "FROM (\n"
+ + " SELECT *\n"
+ + " FROM (\n"
+ + " SELECT k, v, v, ROW_NUMBER() OVER (ORDER BY k DESC)\n"
+ + " FROM set_operation_output_mapping_t\n"
+ + " ) u1\n"
+ + " UNION ALL\n"
+ + " SELECT *\n"
+ + " FROM (\n"
+ + " SELECT k, v, v, ROW_NUMBER() OVER (ORDER BY k DESC)\n"
+ + " FROM set_operation_output_mapping_t\n"
+ + " ) u2\n"
+ + ") u\n"
+ + "LIMIT 10";
+
+ PlanChecker.from(connectContext)
+ .analyze(sql)
+ .rewrite()
+ .matches(
+ logicalUnion(
+ logicalLimit().when(limit -> limit.getLimit()
== 10 && limit.getOffset() == 0),
+ logicalLimit().when(limit -> limit.getLimit()
== 10 && limit.getOffset() == 0)
+ )
+ );
+ }
+}
diff --git
a/regression-test/suites/nereids_rules_p0/merge_one_row_relation/merge_one_row_relation_into_union.groovy
b/regression-test/suites/nereids_rules_p0/merge_one_row_relation/merge_one_row_relation_into_union.groovy
new file mode 100644
index 00000000000..6e865d07c7d
--- /dev/null
+++
b/regression-test/suites/nereids_rules_p0/merge_one_row_relation/merge_one_row_relation_into_union.groovy
@@ -0,0 +1,31 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("merge_one_row_relation_into_union") {
+ sql "SET enable_nereids_planner=true"
+ sql "SET enable_fallback_to_original_planner=false"
+
+ sql """
+ EXPLAIN SHAPE PLAN SELECT v
+ FROM (
+ SELECT CAST('unused' AS CHAR(6)) AS k, CAST('alpha' AS CHAR(6)) AS
v
+ UNION ALL
+ SELECT CAST('unused' AS CHAR(6)), CAST('beta' AS CHAR(6))
+ ) u
+ GROUP BY v
+ """
+}
diff --git
a/regression-test/suites/nereids_rules_p0/push_down_limit_distinct/push_down_limit_distinct_through_union.groovy
b/regression-test/suites/nereids_rules_p0/push_down_limit_distinct/push_down_limit_distinct_through_union.groovy
new file mode 100644
index 00000000000..69fbb8b8ae1
--- /dev/null
+++
b/regression-test/suites/nereids_rules_p0/push_down_limit_distinct/push_down_limit_distinct_through_union.groovy
@@ -0,0 +1,58 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("push_down_limit_distinct_through_union") {
+ sql "set parallel_pipeline_task_num=2"
+ sql "SET enable_nereids_planner=true"
+ sql "SET enable_fallback_to_original_planner=false"
+ sql "set runtime_filter_mode=OFF"
+ sql "SET disable_join_reorder=true"
+ sql "set disable_nereids_rules=PRUNE_EMPTY_PARTITION"
+
+ sql """
+ DROP TABLE IF EXISTS push_down_limit_distinct_union_t;
+ """
+
+ sql """
+ CREATE TABLE IF NOT EXISTS push_down_limit_distinct_union_t (
+ `id` int NULL,
+ `score` int NULL
+ ) ENGINE = OLAP
+ DISTRIBUTED BY HASH(id) BUCKETS 1
+ PROPERTIES (
+ "replication_allocation" = "tag.location.default: 1"
+ );
+ """
+
+ sql """
+ explain shape plan select distinct *
+ from (
+ select *
+ from (
+ select id, score, score, row_number() over (order by id desc)
+ from push_down_limit_distinct_union_t
+ ) u1
+ union all
+ select *
+ from (
+ select id, score, score, row_number() over (order by id desc)
+ from push_down_limit_distinct_union_t
+ ) u2
+ ) u
+ limit 10;
+ """
+}
diff --git
a/regression-test/suites/nereids_rules_p0/push_down_top_n/push_down_top_n_distinct_through_union.groovy
b/regression-test/suites/nereids_rules_p0/push_down_top_n/push_down_top_n_distinct_through_union.groovy
index ce158e07f36..93ef289d723 100644
---
a/regression-test/suites/nereids_rules_p0/push_down_top_n/push_down_top_n_distinct_through_union.groovy
+++
b/regression-test/suites/nereids_rules_p0/push_down_top_n/push_down_top_n_distinct_through_union.groovy
@@ -71,7 +71,26 @@ suite("push_down_top_n_distinct_through_union") {
explain shape plan select * from ((select * from table2 t1 limit 5)
union (select * from table2 t2 limit 5)) sub order by id limit 10;
"""
+ sql """
+ explain shape plan select *
+ from (
+ select *
+ from (
+ select id, score, score, row_number() over (order by id desc)
+ from table2
+ ) u1
+ union
+ select *
+ from (
+ select id, score, score, row_number() over (order by id desc)
+ from table2
+ ) u2
+ ) u
+ order by 1
+ limit 10;
+ """
+
qt_push_down_topn_union_complex_conditions """
explain shape plan select * from (select * from table2 t1 where
t1.score > 10 and t1.name = 'Test' union select * from table2 t2 where t2.id <
5 and t2.score < 20) sub order by id limit 10;
"""
-}
\ No newline at end of file
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]