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

yiguolei 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 16b253a55d3 branch-4.0: [fix](nereids) Allocate fresh ExprId for 
constants when pushing project into Union #62296 (#63025)
16b253a55d3 is described below

commit 16b253a55d3eed6c033a0e0fb023cd8314538399
Author: yujun <[email protected]>
AuthorDate: Mon May 11 15:33:09 2026 +0800

    branch-4.0: [fix](nereids) Allocate fresh ExprId for constants when pushing 
project into Union #62296 (#63025)
    
    cherry pick from #62296
    
    ---------
    
    Co-authored-by: Copilot <[email protected]>
---
 .../rules/rewrite/PushProjectIntoUnion.java        |  16 +-
 .../rules/rewrite/PushProjectThroughUnion.java     |  17 +-
 .../doris/nereids/trees/expressions/Alias.java     |   4 +
 .../rules/rewrite/PushProjectIntoUnionTest.java    | 133 ++++++++++++++++
 .../rules/rewrite/PushProjectThroughUnionTest.java | 122 +++++++++++++++
 .../set_operations/set_operation_exprid_reuse.out  |  44 ++++++
 .../set_operation_exprid_reuse.groovy              | 174 +++++++++++++++++++++
 7 files changed, 506 insertions(+), 4 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushProjectIntoUnion.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushProjectIntoUnion.java
index 78610c949a7..8b109f9dae5 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushProjectIntoUnion.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushProjectIntoUnion.java
@@ -26,6 +26,7 @@ import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.StatementScopeIdGenerator;
 import org.apache.doris.nereids.trees.plans.algebra.SetOperation.Qualifier;
 import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
 import org.apache.doris.nereids.trees.plans.logical.LogicalUnion;
@@ -67,11 +68,24 @@ public class PushProjectIntoUnion extends 
OneRewriteRuleFactory {
                 ImmutableList.Builder<NamedExpression> newProjections = 
ImmutableList.builder();
                 for (NamedExpression old : p.getProjects()) {
                     if (old instanceof SlotReference) {
+                        // replaceRootMap.get(old) is the original constant 
NamedExpression from
+                        // constExprs (each row owns a distinct ExprId, none 
equal to the new
+                        // UNION output ExprId), so it can be reused as-is.
                         newProjections.add((NamedExpression) 
FoldConstantRule.evaluate(replaceRootMap.get(old),
                                 expressionRewriteContext));
                     } else {
+                        // `old` must be an Alias (Nereids LogicalProject 
invariant for non-Slot
+                        // projections). Its ExprId equals the new UNION 
output ExprId, since
+                        // p.getOutput() becomes the UNION output below. 
Feeding the original
+                        // Alias into the rewriter would preserve that outer 
ExprId on every
+                        // constant row and collide with the UNION output. 
Reassign a fresh
+                        // ExprId on the Alias first, then run the SlotRef -> 
constant rewrite
+                        // and constant folding on the new Alias; this 
preserves the Alias'
+                        // name/qualifier/nameFromChild while breaking the 
ExprId collision.
+                        Alias oldAlias = (Alias) old;
+                        Alias reIdAlias = 
oldAlias.withExprId(StatementScopeIdGenerator.newExprId());
                         newProjections.add((NamedExpression) 
FoldConstantRule.evaluate(
-                                ExpressionUtils.replaceNameExpression(old, 
replaceMap), expressionRewriteContext));
+                                ExpressionUtils.replace(reIdAlias, 
replaceMap), expressionRewriteContext));
                     }
                 }
                 newConstExprs.add(newProjections.build());
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushProjectThroughUnion.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushProjectThroughUnion.java
index a07ec94f091..dfaef55b56c 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushProjectThroughUnion.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushProjectThroughUnion.java
@@ -24,6 +24,7 @@ import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.StatementScopeIdGenerator;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
 import org.apache.doris.nereids.trees.plans.logical.LogicalSetOperation;
@@ -80,13 +81,23 @@ public class PushProjectThroughUnion extends 
OneRewriteRuleFactory {
                 ImmutableList.Builder<NamedExpression> newOneRowProject = 
ImmutableList.builder();
                 for (NamedExpression outerProject : projects) {
                     if (outerProject instanceof Slot) {
+                        // replaceMap value is the original constant 
NamedExpression. Each
+                        // constant row owns a distinct ExprId different from 
the new UNION
+                        // output ExprId, so it can be reused as-is.
                         newOneRowProject.add((NamedExpression) 
replaceMap.getOrDefault(outerProject, outerProject));
                     } else {
-                        Expression replacedOutput = outerProject.rewriteUp(e 
-> {
+                        // `outerProject` is an Alias (canPushProject only 
allows Slot or
+                        // Alias(Cast(Slot))). Its ExprId equals the new UNION 
output ExprId
+                        // (newOutput below uses outerProject.toSlot()). 
Reassigning a fresh
+                        // ExprId on the Alias first breaks the collision; the 
subsequent
+                        // rewriteUp only rewrites the inner SlotReferences, 
leaving the
+                        // outer Alias' name/qualifier/nameFromChild intact.
+                        Alias oldAlias = (Alias) outerProject;
+                        Alias reIdAlias = 
oldAlias.withExprId(StatementScopeIdGenerator.newExprId());
+                        newOneRowProject.add((NamedExpression) 
reIdAlias.rewriteUp(e -> {
                             Expression mappingExpr = replaceMap.get(e);
                             return mappingExpr == null ? e : /* remove alias 
*/ mappingExpr.child(0);
-                        });
-                        newOneRowProject.add((NamedExpression) replacedOutput);
+                        }));
                     }
                 }
                 newConstantListBuilder.add(newOneRowProject.build());
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Alias.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Alias.java
index 2ee92281c76..edb416014a6 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Alias.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Alias.java
@@ -171,6 +171,10 @@ public class Alias extends NamedExpression implements 
UnaryExpression {
         return new Alias(exprId, children, name, qualifier, nameFromChild);
     }
 
+    public Alias withExprId(ExprId exprId) {
+        return new Alias(exprId, children, name, qualifier, nameFromChild);
+    }
+
     public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
         return visitor.visitAlias(this, context);
     }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PushProjectIntoUnionTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PushProjectIntoUnionTest.java
new file mode 100644
index 00000000000..42f4f513b8b
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PushProjectIntoUnionTest.java
@@ -0,0 +1,133 @@
+// 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.Multiply;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.algebra.SetOperation.Qualifier;
+import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+import org.apache.doris.nereids.trees.plans.logical.LogicalUnion;
+import org.apache.doris.nereids.types.IntegerType;
+import org.apache.doris.nereids.util.MemoTestUtils;
+import org.apache.doris.nereids.util.PlanChecker;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Plain unit test for {@link PushProjectIntoUnion}: builds a 
Project(LogicalUnion)
+ * tree directly and applies only the rule, so the assertions target the rule's
+ * output without interference from any other rewrite.
+ *
+ * <p>Regression contract: after the rule fires, every constant row must hold
+ * NamedExpressions whose ExprIds are (a) different from the new UNION output
+ * ExprIds and (b) different from the corresponding constants in any other row
+ * of the same column.
+ */
+public class PushProjectIntoUnionTest {
+
+    @Test
+    public void testConstantExprIdsDistinctFromUnionOutputAndAcrossRows() {
+        // Original UNION: outputs=[s#10], no children, three constant rows: 
1, 3, NULL
+        // (already merged from OneRowRelations by an earlier rule). Each 
row's Alias
+        // owns its own ExprId.
+        SlotReference unionOutput = new SlotReference(new ExprId(10), "s",
+                IntegerType.INSTANCE, true, ImmutableList.of());
+        NamedExpression row0 = new Alias(new ExprId(1), new IntegerLiteral(1), 
"1");
+        NamedExpression row1 = new Alias(new ExprId(2), new IntegerLiteral(3), 
"3");
+        NamedExpression row2 = new Alias(new ExprId(3), new IntegerLiteral(7), 
"7");
+        LogicalUnion union = new LogicalUnion(Qualifier.ALL,
+                ImmutableList.of(unionOutput),
+                ImmutableList.of(),
+                ImmutableList.of(ImmutableList.of(row0), 
ImmutableList.of(row1), ImmutableList.of(row2)),
+                false,
+                ImmutableList.of());
+
+        // Parent project: (s * 2) AS n. The Alias here owns ExprId 100, which 
will
+        // become the new UNION output ExprId after the rule fires. Without 
the fix,
+        // this same ExprId would be reused in every constant row.
+        Alias parentAlias = new Alias(new ExprId(100),
+                new Multiply(unionOutput, new IntegerLiteral(2)), "n");
+        LogicalProject<LogicalUnion> project = new LogicalProject<>(
+                ImmutableList.<NamedExpression>of(parentAlias), union);
+
+        Plan rewritten = 
PlanChecker.from(MemoTestUtils.createConnectContext(), project)
+                .applyTopDown(new PushProjectIntoUnion())
+                .getPlan();
+
+        // After the rule fires, the project is absorbed and the root becomes 
a UNION.
+        LogicalUnion newUnion = findUnion(rewritten);
+        Assertions.assertNotNull(newUnion, "expected a LogicalUnion at/under 
the root after rewrite");
+
+        List<? extends NamedExpression> outputs = newUnion.getOutputs();
+        Assertions.assertEquals(1, outputs.size());
+        // The new UNION output keeps the parent project's output ExprId (100).
+        Assertions.assertEquals(parentAlias.getExprId(), 
outputs.get(0).getExprId());
+
+        Set<ExprId> outputIds = new HashSet<>();
+        for (NamedExpression out : outputs) {
+            outputIds.add(out.getExprId());
+        }
+
+        List<List<NamedExpression>> consts = newUnion.getConstantExprsList();
+        Assertions.assertEquals(3, consts.size(), "expected three constant 
rows");
+
+        // (a) constant ExprIds must NOT collide with any UNION output ExprId.
+        for (int row = 0; row < consts.size(); row++) {
+            for (int col = 0; col < consts.get(row).size(); col++) {
+                ExprId cid = consts.get(row).get(col).getExprId();
+                Assertions.assertFalse(outputIds.contains(cid),
+                        "constant ExprId must not collide with UNION output; 
row="
+                                + row + ", col=" + col + ", id=" + cid);
+            }
+        }
+
+        // (b) for each column, all rows must have distinct constant ExprIds.
+        for (int col = 0; col < outputs.size(); col++) {
+            Set<ExprId> seen = new HashSet<>();
+            for (int row = 0; row < consts.size(); row++) {
+                ExprId cid = consts.get(row).get(col).getExprId();
+                Assertions.assertTrue(seen.add(cid),
+                        "constant ExprId duplicated across rows for column " + 
col + ": " + cid);
+            }
+        }
+    }
+
+    private LogicalUnion findUnion(Plan p) {
+        if (p instanceof LogicalUnion) {
+            return (LogicalUnion) p;
+        }
+        for (Plan c : p.children()) {
+            LogicalUnion u = findUnion(c);
+            if (u != null) {
+                return u;
+            }
+        }
+        return null;
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PushProjectThroughUnionTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PushProjectThroughUnionTest.java
new file mode 100644
index 00000000000..328c390d52f
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PushProjectThroughUnionTest.java
@@ -0,0 +1,122 @@
+// 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.Cast;
+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.literal.IntegerLiteral;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.algebra.SetOperation.Qualifier;
+import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+import org.apache.doris.nereids.trees.plans.logical.LogicalUnion;
+import org.apache.doris.nereids.types.BigIntType;
+import org.apache.doris.nereids.types.IntegerType;
+import org.apache.doris.nereids.util.MemoTestUtils;
+import org.apache.doris.nereids.util.PlanChecker;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Plain unit test for {@link PushProjectThroughUnion}: directly builds a
+ * Project(LogicalUnion(...)) where the union holds a non-empty
+ * {@code constantExprsList} (the mixed-union shape produced after
+ * {@code MergeOneRowRelationIntoUnion} folds a {@code LogicalOneRowRelation}
+ * into a union sibling) and asserts that after the rule rewrites the constant
+ * rows, no constant cell's ExprId collides with the new UNION output ExprId.
+ */
+public class PushProjectThroughUnionTest {
+
+    @Test
+    public void testConstantExprIdsDistinctFromUnionOutput() {
+        // Build a LogicalUnion that owns a single constant row and zero 
regular
+        // children. The arity does not affect the constant-row rewrite branch
+        // we want to cover; what matters is that constantExprsList is 
non-empty
+        // and the parent project carries a non-Slot expression that triggers
+        // the else branch of doPushProject.
+        SlotReference unionOutput = new SlotReference(new ExprId(10), "s",
+                IntegerType.INSTANCE, true, ImmutableList.of());
+        NamedExpression row0 = new Alias(new ExprId(1), new 
IntegerLiteral(99), "s");
+        LogicalUnion union = new LogicalUnion(Qualifier.ALL,
+                ImmutableList.of(unionOutput),
+                ImmutableList.of(),
+                ImmutableList.of(ImmutableList.of(row0)),
+                false,
+                ImmutableList.of());
+
+        // Parent project: CAST(s AS BIGINT) AS n. canPushProject accepts this
+        // because the inner expression covered by the cast is a SlotReference.
+        // The Alias's ExprId (100) becomes the new UNION output ExprId via
+        // project.toSlot(). Without the fix, the constant row would also keep
+        // ExprId 100 and collide.
+        Alias parentAlias = new Alias(new ExprId(100),
+                new Cast(unionOutput, BigIntType.INSTANCE), "n");
+        LogicalProject<LogicalUnion> project = new LogicalProject<>(
+                ImmutableList.<NamedExpression>of(parentAlias), union);
+
+        Plan rewritten = 
PlanChecker.from(MemoTestUtils.createConnectContext(), project)
+                .applyTopDown(new PushProjectThroughUnion())
+                .getPlan();
+
+        LogicalUnion newUnion = findUnion(rewritten);
+        Assertions.assertNotNull(newUnion, "expected a LogicalUnion at/under 
the root after rewrite");
+
+        List<? extends NamedExpression> outputs = newUnion.getOutputs();
+        Assertions.assertEquals(1, outputs.size());
+        // The new UNION output reuses the parent project's output ExprId.
+        Assertions.assertEquals(parentAlias.getExprId(), 
outputs.get(0).getExprId());
+
+        Set<ExprId> outputIds = new HashSet<>();
+        for (NamedExpression out : outputs) {
+            outputIds.add(out.getExprId());
+        }
+
+        List<List<NamedExpression>> consts = newUnion.getConstantExprsList();
+        Assertions.assertEquals(1, consts.size(), "expected one constant row");
+
+        for (int row = 0; row < consts.size(); row++) {
+            for (int col = 0; col < consts.get(row).size(); col++) {
+                ExprId cid = consts.get(row).get(col).getExprId();
+                Assertions.assertFalse(outputIds.contains(cid),
+                        "constant ExprId must not collide with UNION output; 
row="
+                                + row + ", col=" + col + ", id=" + cid);
+            }
+        }
+    }
+
+    private LogicalUnion findUnion(Plan p) {
+        if (p instanceof LogicalUnion) {
+            return (LogicalUnion) p;
+        }
+        for (Plan c : p.children()) {
+            LogicalUnion u = findUnion(c);
+            if (u != null) {
+                return u;
+            }
+        }
+        return null;
+    }
+}
diff --git 
a/regression-test/data/query_p0/set_operations/set_operation_exprid_reuse.out 
b/regression-test/data/query_p0/set_operations/set_operation_exprid_reuse.out
new file mode 100644
index 00000000000..bf8c790057c
--- /dev/null
+++ 
b/regression-test/data/query_p0/set_operations/set_operation_exprid_reuse.out
@@ -0,0 +1,44 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !intersect_cte_expr --
+\N
+2
+
+-- !except_cte_expr --
+6
+
+-- !union_distinct_cte_expr --
+\N
+2
+4
+6
+
+-- !intersect_cast --
+\N
+1
+
+-- !intersect_rename --
+\N
+1
+
+-- !nested_set_ops --
+1
+2
+
+-- !intersect_multi_col --
+20     Y
+30     Z
+
+-- !intersect_nulls --
+\N
+1
+
+-- !chained_intersect --
+6
+
+-- !intersect_table --
+4      B
+6      C
+
+-- !except_table --
+2      A
+
diff --git 
a/regression-test/suites/query_p0/set_operations/set_operation_exprid_reuse.groovy
 
b/regression-test/suites/query_p0/set_operations/set_operation_exprid_reuse.groovy
new file mode 100644
index 00000000000..97b36ca1e34
--- /dev/null
+++ 
b/regression-test/suites/query_p0/set_operations/set_operation_exprid_reuse.groovy
@@ -0,0 +1,174 @@
+// 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.
+
+// Regression test for ExprId reuse between set operation output and child 
output.
+// When Nereids optimizer produces a UNION with a single regular child where 
the
+// output ExprId equals the child's output ExprId, the plan translator must 
translate
+// child result expressions before creating the output tuple descriptor to 
avoid
+// the ExprId->SlotRef mapping being overwritten.
+suite("set_operation_exprid_reuse", "query,p0") {
+    sql """
+        SET enable_nereids_planner=true;
+        SET enable_fallback_to_original_planner=false;
+    """
+
+    // Original reproducing query from CIR-19889:
+    // CTE + INTERSECT with expression computation causes ExprId reuse
+    order_qt_intersect_cte_expr """
+        WITH
+            tbl0(`n`) AS (SELECT 1 UNION ALL SELECT 3 UNION ALL SELECT NULL),
+            tbl1(`n`) AS (SELECT 2 UNION ALL SELECT NULL UNION ALL SELECT 1)
+        (
+            SELECT (`n` * 2) AS `n` FROM tbl0
+            INTERSECT
+            SELECT (`n` * 2) AS `n` FROM tbl1
+        )
+    """
+
+    // EXCEPT with CTE and expression
+    order_qt_except_cte_expr """
+        WITH
+            tbl0(`n`) AS (SELECT 1 UNION ALL SELECT 3 UNION ALL SELECT NULL),
+            tbl1(`n`) AS (SELECT 2 UNION ALL SELECT NULL UNION ALL SELECT 1)
+        (
+            SELECT (`n` * 2) AS `n` FROM tbl0
+            EXCEPT
+            SELECT (`n` * 2) AS `n` FROM tbl1
+        )
+    """
+
+    // UNION DISTINCT with CTE and expression
+    order_qt_union_distinct_cte_expr """
+        WITH
+            tbl0(`n`) AS (SELECT 1 UNION ALL SELECT 3 UNION ALL SELECT NULL),
+            tbl1(`n`) AS (SELECT 2 UNION ALL SELECT NULL UNION ALL SELECT 1)
+        (
+            SELECT (`n` * 2) AS `n` FROM tbl0
+            UNION
+            SELECT (`n` * 2) AS `n` FROM tbl1
+        )
+    """
+
+    // CAST projection through Union (PushProjectThroughUnion path)
+    order_qt_intersect_cast """
+        WITH
+            tbl0(`n`) AS (SELECT 1 UNION ALL SELECT 3 UNION ALL SELECT NULL),
+            tbl1(`n`) AS (SELECT 2 UNION ALL SELECT NULL UNION ALL SELECT 1)
+        (
+            SELECT CAST(`n` AS BIGINT) AS `n` FROM tbl0
+            INTERSECT
+            SELECT CAST(`n` AS BIGINT) AS `n` FROM tbl1
+        )
+    """
+
+    // Plain rename projection through Union (PushProjectThroughUnion slot 
path)
+    order_qt_intersect_rename """
+        WITH
+            tbl0(`n`) AS (SELECT 1 UNION ALL SELECT 3 UNION ALL SELECT NULL),
+            tbl1(`n`) AS (SELECT 2 UNION ALL SELECT NULL UNION ALL SELECT 1)
+        (
+            SELECT `n` AS `m` FROM tbl0
+            INTERSECT
+            SELECT `n` AS `m` FROM tbl1
+        )
+    """
+
+    // Nested set operations
+    order_qt_nested_set_ops """
+        SELECT * FROM (SELECT 1 a INTERSECT SELECT 1 a) t1
+        UNION
+        SELECT * FROM (SELECT 2 a EXCEPT SELECT 3 a) t2
+    """
+
+    // INTERSECT with multiple columns and expressions
+    order_qt_intersect_multi_col """
+        WITH
+            t1(a, b) AS (SELECT 1, 'x' UNION ALL SELECT 2, 'y' UNION ALL 
SELECT 3, 'z'),
+            t2(a, b) AS (SELECT 2, 'y' UNION ALL SELECT 3, 'z' UNION ALL 
SELECT 4, 'w')
+        (
+            SELECT a * 10 AS a, upper(b) AS b FROM t1
+            INTERSECT
+            SELECT a * 10 AS a, upper(b) AS b FROM t2
+        )
+    """
+
+    // INTERSECT with NULL handling
+    order_qt_intersect_nulls """
+        WITH
+            t1(a) AS (SELECT NULL UNION ALL SELECT NULL UNION ALL SELECT 1),
+            t2(a) AS (SELECT NULL UNION ALL SELECT 1 UNION ALL SELECT 2)
+        (
+            SELECT a FROM t1
+            INTERSECT
+            SELECT a FROM t2
+        )
+    """
+
+    // Multiple INTERSECT chained
+    order_qt_chained_intersect """
+        WITH
+            t1(n) AS (SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3),
+            t2(n) AS (SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4),
+            t3(n) AS (SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5)
+        (
+            SELECT n * 2 AS n FROM t1
+            INTERSECT
+            SELECT n * 2 AS n FROM t2
+            INTERSECT
+            SELECT n * 2 AS n FROM t3
+        )
+    """
+
+    // INTERSECT with table data (not just constants)
+    sql "drop table if exists set_op_exprid_t1"
+    sql "drop table if exists set_op_exprid_t2"
+
+    sql """
+        CREATE TABLE set_op_exprid_t1 (
+            id INT NOT NULL,
+            val VARCHAR(50) NOT NULL
+        ) ENGINE=OLAP
+        DUPLICATE KEY(id)
+        DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES ("replication_num" = "1")
+    """
+
+    sql """
+        CREATE TABLE set_op_exprid_t2 (
+            id INT NOT NULL,
+            val VARCHAR(50) NOT NULL
+        ) ENGINE=OLAP
+        DUPLICATE KEY(id)
+        DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES ("replication_num" = "1")
+    """
+
+    sql "INSERT INTO set_op_exprid_t1 VALUES (1, 'a'), (2, 'b'), (3, 'c')"
+    sql "INSERT INTO set_op_exprid_t2 VALUES (2, 'b'), (3, 'c'), (4, 'd')"
+
+    order_qt_intersect_table """
+        SELECT id * 2, upper(val) FROM set_op_exprid_t1
+        INTERSECT
+        SELECT id * 2, upper(val) FROM set_op_exprid_t2
+    """
+
+    order_qt_except_table """
+        SELECT id * 2, upper(val) FROM set_op_exprid_t1
+        EXCEPT
+        SELECT id * 2, upper(val) FROM set_op_exprid_t2
+    """
+}


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

Reply via email to