This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.2 by this push:
     new 432420eac78 branch-4.2: [fix](rbo) Preserve column mappings when 
merging nested set operations #69131 (#69428)
432420eac78 is described below

commit 432420eac78f7c536dff74efce21040408b82bac
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu Oct 22 18:42:55 2026 +0800

    branch-4.2: [fix](rbo) Preserve column mappings when merging nested set 
operations #69131 (#69428)
    
    Cherry-picked from #69131
    
    Co-authored-by: feiniaofeiafei <[email protected]>
---
 .../nereids/rules/rewrite/MergeSetOperations.java  | 41 +++++++++-
 .../rules/rewrite/MergeSetOperationsExcept.java    |  5 +-
 .../rewrite/SetOperationOutputMappingTest.java     | 53 +++++++++++++
 .../merge_union_output_mapping.out                 | 12 +++
 .../merge_union_output_mapping.groovy              | 88 ++++++++++++++++++++++
 5 files changed, 195 insertions(+), 4 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeSetOperations.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeSetOperations.java
index 57fedeb350e..53817b658e4 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeSetOperations.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeSetOperations.java
@@ -19,6 +19,7 @@ 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.ExprId;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
 import org.apache.doris.nereids.trees.expressions.SlotReference;
 import org.apache.doris.nereids.trees.plans.Plan;
@@ -27,9 +28,12 @@ import 
org.apache.doris.nereids.trees.plans.logical.LogicalIntersect;
 import org.apache.doris.nereids.trees.plans.logical.LogicalSetOperation;
 import org.apache.doris.nereids.trees.plans.logical.LogicalUnion;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Merge nodes of the same type and same qualifier.
@@ -83,8 +87,13 @@ public class MergeSetOperations implements 
RewriteRuleFactory {
             if (canMerge(parentUnion, child) && child.getOutput().size() == 
parentUnion.getOutput().size()) {
                 LogicalUnion childUnion = (LogicalUnion) child;
                 newChildren.addAll(childUnion.children());
-                
newChildrenOutputs.addAll(childUnion.getRegularChildrenOutputs());
-                constantList.addAll(childUnion.getConstantExprsList());
+                List<Integer> outputOrder = getChildOutputOrder(parentUnion, 
i, childUnion);
+                for (List<SlotReference> outputs : 
childUnion.getRegularChildrenOutputs()) {
+                    newChildrenOutputs.add(reorderOutputs(outputs, 
outputOrder));
+                }
+                for (List<NamedExpression> constants : 
childUnion.getConstantExprsList()) {
+                    constantList.add(reorderOutputs(constants, outputOrder));
+                }
                 needMerge = true;
             } else {
                 newChildren.add(child);
@@ -108,7 +117,10 @@ public class MergeSetOperations implements 
RewriteRuleFactory {
             if (canMerge(parentSetIntersect, child)) {
                 LogicalIntersect childIntersect = (LogicalIntersect) child;
                 newChildren.addAll(childIntersect.children());
-                
newChildrenOutputs.addAll(childIntersect.getRegularChildrenOutputs());
+                List<Integer> outputOrder = 
getChildOutputOrder(parentSetIntersect, i, childIntersect);
+                for (List<SlotReference> outputs : 
childIntersect.getRegularChildrenOutputs()) {
+                    newChildrenOutputs.add(reorderOutputs(outputs, 
outputOrder));
+                }
             } else {
                 newChildren.add(child);
                 
newChildrenOutputs.add(parentSetIntersect.getRegularChildOutput(i));
@@ -118,6 +130,29 @@ public class MergeSetOperations implements 
RewriteRuleFactory {
                 newChildren.build(), newChildrenOutputs.build());
     }
 
+    static List<Integer> getChildOutputOrder(
+            LogicalSetOperation parent, int childIndex, LogicalSetOperation 
child) {
+        Map<ExprId, Integer> outputIndexes = new HashMap<>();
+        for (int i = 0; i < child.getOutput().size(); i++) {
+            outputIndexes.put(child.getOutput().get(i).getExprId(), i);
+        }
+        ImmutableList.Builder<Integer> order = ImmutableList.builder();
+        for (SlotReference slot : parent.getRegularChildOutput(childIndex)) {
+            Integer index = outputIndexes.get(slot.getExprId());
+            Preconditions.checkState(index != null, "Set operation input must 
belong to child output");
+            order.add(index);
+        }
+        return order.build();
+    }
+
+    static <T> List<T> reorderOutputs(List<T> outputs, List<Integer> order) {
+        ImmutableList.Builder<T> reordered = 
ImmutableList.builderWithExpectedSize(order.size());
+        for (int index : order) {
+            reordered.add(outputs.get(index));
+        }
+        return reordered.build();
+    }
+
     /** canMerge */
     public static boolean canMerge(LogicalSetOperation parent) {
         Plan left = parent.child(0);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeSetOperationsExcept.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeSetOperationsExcept.java
index 2eb284e69c5..79d62285bca 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeSetOperationsExcept.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeSetOperationsExcept.java
@@ -54,7 +54,10 @@ public class MergeSetOperationsExcept extends 
OneRewriteRuleFactory {
 
                     newChildren.addAll(childExcept.children());
                     newChildren.addAll(parentExcept.children().subList(1, 
parentExcept.children().size()));
-                    
newChildrenOutputs.addAll(childExcept.getRegularChildrenOutputs());
+                    List<Integer> outputOrder = 
MergeSetOperations.getChildOutputOrder(parentExcept, 0, childExcept);
+                    for (List<SlotReference> outputs : 
childExcept.getRegularChildrenOutputs()) {
+                        
newChildrenOutputs.add(MergeSetOperations.reorderOutputs(outputs, outputOrder));
+                    }
                     newChildrenOutputs.addAll(
                             
parentExcept.getRegularChildrenOutputs().subList(1, 
parentExcept.children().size()));
 
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
index a42c974dabf..e1d4489e18c 100644
--- 
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
@@ -26,6 +26,7 @@ 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.LogicalExcept;
 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;
@@ -63,6 +64,58 @@ class SetOperationOutputMappingTest extends 
TestWithFeService implements MemoPat
         StatementScopeIdGenerator.clear();
     }
 
+    @Test
+    void testMergeSetOperationsComposesChildOutputMapping() {
+        Alias first = new Alias(new ExprId(101), new IntegerLiteral(10), 
"campaign");
+        Alias second = new Alias(new ExprId(102), new IntegerLiteral(20), 
"country");
+        LogicalOneRowRelation row = new LogicalOneRowRelation(
+                new RelationId(101), ImmutableList.of(first, second));
+        SlotReference campaign = new SlotReference(new ExprId(103), "campaign",
+                IntegerType.INSTANCE, false, ImmutableList.of());
+        SlotReference country = new SlotReference(new ExprId(104), "country",
+                IntegerType.INSTANCE, false, ImmutableList.of());
+        LogicalUnion child = new LogicalUnion(Qualifier.ALL, 
ImmutableList.of(campaign, country),
+                ImmutableList.of(ImmutableList.of((SlotReference) 
first.toSlot(), (SlotReference) second.toSlot())),
+                ImmutableList.of(ImmutableList.of(first, second)), false, 
ImmutableList.of(row));
+        LogicalUnion parent = new LogicalUnion(Qualifier.ALL, 
ImmutableList.of(country, campaign),
+                ImmutableList.of(ImmutableList.of(country, campaign)), 
ImmutableList.of(),
+                false, ImmutableList.of(child));
+
+        LogicalUnion rewritten = (LogicalUnion) 
PlanChecker.from(MemoTestUtils.createConnectContext(), parent)
+                .applyTopDown(new MergeSetOperations()).getPlan();
+        Assertions.assertEquals(ImmutableList.of(row), rewritten.children());
+        Assertions.assertEquals(ImmutableList.of(second.toSlot(), 
first.toSlot()),
+                rewritten.getRegularChildOutput(0));
+        Assertions.assertEquals(ImmutableList.of(second, first), 
rewritten.getConstantExprsList().get(0));
+
+        Alias rightFirst = new Alias(new ExprId(105), new IntegerLiteral(30), 
"right_first");
+        Alias rightSecond = new Alias(new ExprId(106), new IntegerLiteral(40), 
"right_second");
+        LogicalOneRowRelation right = new LogicalOneRowRelation(
+                new RelationId(102), ImmutableList.of(rightFirst, 
rightSecond));
+        List<SlotReference> rightOutputs = ImmutableList.of(
+                (SlotReference) rightFirst.toSlot(), (SlotReference) 
rightSecond.toSlot());
+        LogicalExcept childExcept = new LogicalExcept(Qualifier.DISTINCT, 
ImmutableList.of(campaign, country),
+                ImmutableList.of(child.getRegularChildOutput(0), 
rightOutputs), ImmutableList.of(row, right));
+        Alias outerFirst = new Alias(new ExprId(107), new IntegerLiteral(50), 
"outer_first");
+        Alias outerSecond = new Alias(new ExprId(108), new IntegerLiteral(60), 
"outer_second");
+        LogicalOneRowRelation outerRight = new LogicalOneRowRelation(
+                new RelationId(103), ImmutableList.of(outerFirst, 
outerSecond));
+        List<SlotReference> outerRightOutputs = ImmutableList.of(
+                (SlotReference) outerFirst.toSlot(), (SlotReference) 
outerSecond.toSlot());
+        LogicalExcept parentExcept = new LogicalExcept(Qualifier.DISTINCT, 
ImmutableList.of(country, campaign),
+                ImmutableList.of(ImmutableList.of(country, campaign), 
outerRightOutputs),
+                ImmutableList.of(childExcept, outerRight));
+        LogicalExcept rewrittenExcept = (LogicalExcept) PlanChecker.from(
+                MemoTestUtils.createConnectContext(), parentExcept)
+                .applyTopDown(new MergeSetOperationsExcept()).getPlan();
+        Assertions.assertEquals(ImmutableList.of(row, right, outerRight), 
rewrittenExcept.children());
+        Assertions.assertEquals(ImmutableList.of(second.toSlot(), 
first.toSlot()),
+                rewrittenExcept.getRegularChildOutput(0));
+        Assertions.assertEquals(ImmutableList.of(rightSecond.toSlot(), 
rightFirst.toSlot()),
+                rewrittenExcept.getRegularChildOutput(1));
+        Assertions.assertEquals(outerRightOutputs, 
rewrittenExcept.getRegularChildOutput(2));
+    }
+
     @Test
     void testMergeOneRowRelationUsesRegularChildOutput() {
         Alias firstProject = new Alias(new ExprId(1), new IntegerLiteral(10), 
"first_col");
diff --git 
a/regression-test/data/nereids_rules_p0/merge_one_row_relation/merge_union_output_mapping.out
 
b/regression-test/data/nereids_rules_p0/merge_one_row_relation/merge_union_output_mapping.out
new file mode 100644
index 00000000000..41dab4ef646
--- /dev/null
+++ 
b/regression-test/data/nereids_rules_p0/merge_one_row_relation/merge_union_output_mapping.out
@@ -0,0 +1,12 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !country --
+FR
+
+-- !country_date --
+FR     2026-01-01
+
+-- !except_reordered --
+FR     AD_A
+
+-- !except_removed --
+
diff --git 
a/regression-test/suites/nereids_rules_p0/merge_one_row_relation/merge_union_output_mapping.groovy
 
b/regression-test/suites/nereids_rules_p0/merge_one_row_relation/merge_union_output_mapping.groovy
new file mode 100644
index 00000000000..6a95bceba9a
--- /dev/null
+++ 
b/regression-test/suites/nereids_rules_p0/merge_one_row_relation/merge_union_output_mapping.groovy
@@ -0,0 +1,88 @@
+// 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_union_output_mapping", "p0") {
+    sql "DROP TABLE IF EXISTS merge_union_output_mapping"
+    sql """CREATE TABLE merge_union_output_mapping (
+        id INT, campaign VARCHAR(40), country VARCHAR(40), dt VARCHAR(40)
+    ) DUPLICATE KEY(id) DISTRIBUTED BY HASH(id) BUCKETS 1
+    PROPERTIES ('replication_num' = '1')"""
+    sql "INSERT INTO merge_union_output_mapping VALUES (1, 'AD_A', 'FR', 
'2026-01-01')"
+    sql "SET enable_sql_cache=false"
+    order_qt_country """WITH sa AS (
+    SELECT CAST(dt AS DATE) AS dt, campaign, country, campaign AS tag FROM 
merge_union_output_mapping
+    UNION ALL
+    SELECT CAST(dt AS DATE), campaign, country, campaign
+    FROM (
+        SELECT campaign, country, dt, id AS x0, id AS x1 FROM 
merge_union_output_mapping
+        UNION ALL
+        SELECT campaign, country, dt, id AS x0, id AS x1 FROM 
merge_union_output_mapping
+    ) u
+), c AS (
+    SELECT dt, country, tag, 'SA' AS ad_type FROM sa
+), e AS (
+    SELECT 'FR' AS country
+)
+SELECT a.country
+FROM c a
+LEFT JOIN e ON CASE
+    WHEN a.country = 'PL' AND a.ad_type = 'DSP' THEN 'PL_DSP'
+    ELSE a.country
+END = e.country
+LEFT JOIN c b ON a.tag = b.tag
+GROUP BY a.country"""
+    order_qt_country_date """WITH sa AS (
+    SELECT CAST(dt AS DATE) AS dt, campaign, country, campaign AS tag FROM 
merge_union_output_mapping
+    UNION ALL
+    SELECT CAST(dt AS DATE), campaign, country, campaign
+    FROM (
+        SELECT campaign, country, dt, id AS x0, id AS x1 FROM 
merge_union_output_mapping
+        UNION ALL
+        SELECT campaign, country, dt, id AS x0, id AS x1 FROM 
merge_union_output_mapping
+    ) u
+), c AS (
+    SELECT dt, country, tag, 'SA' AS ad_type FROM sa
+), e AS (
+    SELECT 'FR' AS country
+)
+SELECT a.country, a.dt
+FROM c a
+LEFT JOIN e ON CASE
+    WHEN a.country = 'PL' AND a.ad_type = 'DSP' THEN 'PL_DSP'
+    ELSE a.country
+END = e.country
+LEFT JOIN c b ON a.tag = b.tag
+GROUP BY a.country, a.dt"""
+    order_qt_except_reordered """
+        SELECT country, campaign FROM (
+            SELECT campaign, country FROM merge_union_output_mapping
+            EXCEPT
+            SELECT campaign, country FROM merge_union_output_mapping WHERE id 
< 0
+        ) u
+        EXCEPT
+        SELECT country, campaign FROM merge_union_output_mapping WHERE id < 0
+    """
+    order_qt_except_removed """
+        SELECT country, campaign FROM (
+            SELECT campaign, country FROM merge_union_output_mapping
+            EXCEPT
+            SELECT campaign, country FROM merge_union_output_mapping WHERE id 
< 0
+        ) u
+        EXCEPT
+        SELECT country, campaign FROM merge_union_output_mapping
+    """
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to