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

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


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new 00fdd510f51 branch-4.1: [fix](SVGuard) Preserve session guards during 
common expression extraction #67717 (#67863)
00fdd510f51 is described below

commit 00fdd510f51f5d971f9b1428b5091a0f5a54fbae
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sat Sep 12 00:51:58 2026 +0800

    branch-4.1: [fix](SVGuard) Preserve session guards during common expression 
extraction #67717 (#67863)
    
    Cherry-picked from #67717
    
    Co-authored-by: feiniaofeiafei <[email protected]>
---
 .../processor/post/CommonSubExpressionOpt.java     |  19 +++-
 .../postprocess/CommonSubExpressionTest.java       | 105 +++++++++++++++++++++
 .../data/nereids_p0/test_cse_session_var_guard.out |  64 +++++++++++++
 .../nereids_p0/test_cse_session_var_guard.groovy   |  77 +++++++++++++++
 4 files changed, 264 insertions(+), 1 deletion(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/CommonSubExpressionOpt.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/CommonSubExpressionOpt.java
index 0d9e3abc25a..f5999111de1 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/CommonSubExpressionOpt.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/CommonSubExpressionOpt.java
@@ -21,6 +21,7 @@ import org.apache.doris.nereids.CascadesContext;
 import org.apache.doris.nereids.trees.expressions.Alias;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.SessionVarGuardExpr;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import 
org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionRewriter;
 import org.apache.doris.nereids.trees.plans.Plan;
@@ -76,12 +77,16 @@ public class CommonSubExpressionOpt extends 
PlanPostProcessor {
                 layer.addAll(inputSlots);
                 Set<Expression> exprsInDepth = CommonSubExpressionCollector
                         .getExpressionsFromDepthMap(i, 
collector.commonExprByDepth);
+                Map<Expression, Alias> currentLayerAliases = new 
LinkedHashMap<>();
                 exprsInDepth.forEach(expr -> {
+                    // Only reference aliases produced by earlier layers.
                     Expression rewritten = 
expr.accept(ExpressionReplacer.INSTANCE, aliasMap);
                     // if rewritten is already alias, use it directly, because 
in materialized view rewriting
                     // Should keep out slot immutably after rewritten 
successfully
-                    aliasMap.put(expr, rewritten instanceof Alias ? (Alias) 
rewritten : new Alias(rewritten));
+                    currentLayerAliases.put(expr,
+                            rewritten instanceof Alias ? (Alias) rewritten : 
new Alias(rewritten));
                 });
+                aliasMap.putAll(currentLayerAliases);
                 for (Alias alias : aliasMap.values()) {
                     if (previousAlias.contains(alias)) {
                         layer.add(alias.toSlot());
@@ -124,5 +129,17 @@ public class CommonSubExpressionOpt extends 
PlanPostProcessor {
             }
             return super.visit(expr, replaceMap);
         }
+
+        @Override
+        public Expression visitSessionVarGuardExpr(SessionVarGuardExpr expr,
+                Map<? extends Expression, ? extends Alias> replaceMap) {
+            if (replaceMap.containsKey(expr)) {
+                return replaceMap.get(expr).toSlot();
+            }
+            // Match the collector: the guard and its wrapped root form one 
CSE unit.
+            // Replacing the wrapped root would lose its session variable 
protection.
+            Expression child = rewriteChildren(this, expr.child(), replaceMap);
+            return child == expr.child() ? expr : expr.withChildren(child);
+        }
     }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/CommonSubExpressionTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/CommonSubExpressionTest.java
index 81f5c291684..4b60b3d9eec 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/CommonSubExpressionTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/CommonSubExpressionTest.java
@@ -24,21 +24,29 @@ import org.apache.doris.nereids.trees.expressions.Add;
 import org.apache.doris.nereids.trees.expressions.Alias;
 import org.apache.doris.nereids.trees.expressions.And;
 import org.apache.doris.nereids.trees.expressions.ArrayItemReference;
+import org.apache.doris.nereids.trees.expressions.EqualTo;
 import org.apache.doris.nereids.trees.expressions.ExprId;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.SessionVarGuardExpr;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.expressions.SlotReference;
 import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayMap;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.Coalesce;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.If;
 import org.apache.doris.nereids.trees.expressions.functions.scalar.Lambda;
 import 
org.apache.doris.nereids.trees.expressions.functions.scalar.ShortCircuitIf;
 import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
+import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.StringLiteral;
 import 
org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionRewriter;
 import org.apache.doris.nereids.types.ArrayType;
 import org.apache.doris.nereids.types.IntegerType;
+import org.apache.doris.nereids.types.VarcharType;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -124,6 +132,103 @@ public class CommonSubExpressionTest extends 
ExpressionRewriteTestHelper {
 
     }
 
+    @Test
+    public void testGuardDoesNotReuseUnguardedRoot() {
+        Slot platform = new SlotReference("platform", 
VarcharType.createVarcharType(65533));
+        Expression coalesce = new Coalesce(platform, new StringLiteral(""));
+        SessionVarGuardExpr guard = new SessionVarGuardExpr(coalesce,
+                ImmutableMap.of("enable_decimal256", "false"));
+        Alias unguarded = new Alias(coalesce, "unguarded");
+
+        // Even an available alias for the root cannot replace the protected 
computation.
+        Assertions.assertEquals(guard, 
guard.accept(CommonSubExpressionOpt.ExpressionReplacer.INSTANCE,
+                ImmutableMap.of(coalesce, unguarded)));
+
+        SessionVarGuardExpr otherGuard = new SessionVarGuardExpr(coalesce,
+                ImmutableMap.of("enable_decimal256", "true"));
+        Assertions.assertEquals(guard, 
guard.accept(CommonSubExpressionOpt.ExpressionReplacer.INSTANCE,
+                ImmutableMap.of(otherGuard, new Alias(otherGuard, 
"other_session"))));
+    }
+
+    @Test
+    public void testReuseWholeGuardAndGuardedArgument() {
+        Slot platform = new SlotReference("platform", 
VarcharType.createVarcharType(65533));
+        Map<String, String> sessionVars = ImmutableMap.of("enable_decimal256", 
"false");
+        SessionVarGuardExpr inner = new SessionVarGuardExpr(
+                new Coalesce(platform, new StringLiteral("")), sessionVars);
+        Alias computed = new Alias(inner, "computed");
+        Map<Expression, Alias> aliases = ImmutableMap.of(inner, computed);
+
+        Assertions.assertEquals(computed.toSlot(),
+                
inner.accept(CommonSubExpressionOpt.ExpressionReplacer.INSTANCE, aliases));
+
+        SessionVarGuardExpr outer = new SessionVarGuardExpr(
+                new If(new EqualTo(inner, new StringLiteral("")), 
NullLiteral.INSTANCE, inner), sessionVars);
+        Expression expected = new SessionVarGuardExpr(
+                new If(new EqualTo(computed.toSlot(), new StringLiteral("")),
+                        NullLiteral.INSTANCE, computed.toSlot()), sessionVars);
+        Assertions.assertEquals(expected,
+                
outer.accept(CommonSubExpressionOpt.ExpressionReplacer.INSTANCE, aliases));
+    }
+
+    @Test
+    public void testGuardCseProjectionDependencies() throws Exception {
+        Slot platform = new SlotReference("platform", 
VarcharType.createVarcharType(65533));
+        Expression coalesce = new Coalesce(platform, new StringLiteral(""));
+        Map<String, String> sessionVars = ImmutableMap.of("enable_decimal256", 
"false");
+        SessionVarGuardExpr guardedCoalesce = new 
SessionVarGuardExpr(coalesce, sessionVars);
+        Expression guardedIf = new SessionVarGuardExpr(new If(
+                new EqualTo(guardedCoalesce, new StringLiteral("")), 
NullLiteral.INSTANCE, guardedCoalesce),
+                sessionVars);
+        Alias x = new Alias(coalesce, "x");
+        Alias y = new Alias(coalesce, "y");
+        Alias z = new Alias(guardedIf, "z");
+        Method method = CommonSubExpressionOpt.class
+                .getDeclaredMethod("computeMultiLayerProjections", Set.class, 
List.class);
+        method.setAccessible(true);
+
+        // Use the expanded alias-function reproducer in both projection 
orders.
+        for (List<NamedExpression> projects : ImmutableList.of(
+                ImmutableList.<NamedExpression>of(x, y, z), 
ImmutableList.<NamedExpression>of(z, x, y))) {
+            List<List<NamedExpression>> layers = (List<List<NamedExpression>>) 
method.invoke(
+                    new CommonSubExpressionOpt(), coalesce.getInputSlots(), 
projects);
+            Assertions.assertEquals(2, layers.size());
+            Map<Expression, Alias> extracted = new HashMap<>();
+            for (NamedExpression expression : layers.get(0)) {
+                if (expression instanceof Alias) {
+                    extracted.put(expression.child(0), (Alias) expression);
+                }
+            }
+            Assertions.assertEquals(2, extracted.size());
+            Assertions.assertTrue(extracted.containsKey(coalesce));
+            Assertions.assertTrue(extracted.containsKey(guardedCoalesce));
+
+            // Check the entire layer before making any of its outputs 
available.
+            Set<Slot> inputs = new HashSet<>(coalesce.getInputSlots());
+            for (List<NamedExpression> layer : layers) {
+                Set<Slot> outputs = new HashSet<>();
+                for (NamedExpression expression : layer) {
+                    
Assertions.assertTrue(inputs.containsAll(expression.getInputSlots()),
+                            "Projection references an unavailable input: " + 
expression);
+                    outputs.add(expression.toSlot());
+                }
+                inputs = outputs;
+            }
+            Slot guardedSlot = extracted.get(guardedCoalesce).toSlot();
+            Expression expectedIf = new SessionVarGuardExpr(new If(
+                    new EqualTo(guardedSlot, new StringLiteral("")), 
NullLiteral.INSTANCE, guardedSlot), sessionVars);
+            Map<ExprId, Expression> expected = ImmutableMap.of(
+                    x.getExprId(), extracted.get(coalesce).toSlot(),
+                    y.getExprId(), extracted.get(coalesce).toSlot(), 
z.getExprId(), expectedIf);
+            for (int i = 0; i < projects.size(); i++) {
+                NamedExpression output = layers.get(1).get(i);
+                Assertions.assertEquals(projects.get(i).getExprId(), 
output.getExprId());
+                Assertions.assertEquals(projects.get(i).getName(), 
output.getName());
+                Assertions.assertEquals(expected.get(output.getExprId()), 
output.child(0));
+            }
+        }
+    }
+
     private void assertExpression(Expression expr, String str) {
         Assertions.assertEquals(ExprParser.INSTANCE.parseExpression(str), 
expr);
     }
diff --git a/regression-test/data/nereids_p0/test_cse_session_var_guard.out 
b/regression-test/data/nereids_p0/test_cse_session_var_guard.out
new file mode 100644
index 00000000000..d1faf94beb0
--- /dev/null
+++ b/regression-test/data/nereids_p0/test_cse_session_var_guard.out
@@ -0,0 +1,64 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !without_guard --
+1      abc     abc     abc
+2                      \N
+3                      \N
+4      xyz     xyz     xyz
+5      abc     abc     abc
+6                       
+7      中文      中文      中文
+
+-- !with_guard --
+1      abc     abc     abc
+2                      \N
+3                      \N
+4      xyz     xyz     xyz
+5      abc     abc     abc
+6                       
+7      中文      中文      中文
+
+-- !guard_first --
+1      abc     abc     abc
+2      \N              
+3      \N              
+4      xyz     xyz     xyz
+5      abc     abc     abc
+6                       
+7      中文      中文      中文
+
+-- !repeated_guard --
+1      abc     abc     abc     abc
+2      \N      \N              
+3      \N      \N              
+4      xyz     xyz     xyz     xyz
+5      abc     abc     abc     abc
+6                               
+7      中文      中文      中文      中文
+
+-- !nested_guard --
+1      fallback        fallback        fallback
+2                      \N
+3      fallback        fallback        fallback
+4      xyz     xyz     xyz
+5      fallback        fallback        fallback
+6                       
+7      中文      中文      中文
+
+-- !multiple_layers --
+1      2       4       6
+2      3       6       9
+3      4       8       12
+4      5       10      15
+5      6       12      18
+6      7       14      21
+7      8       16      24
+
+-- !reverse_session_guard --
+1      abc     abc     abc
+2                      \N
+3                      \N
+4      xyz     xyz     xyz
+5      abc     abc     abc
+6                       
+7      中文      中文      中文
+
diff --git 
a/regression-test/suites/nereids_p0/test_cse_session_var_guard.groovy 
b/regression-test/suites/nereids_p0/test_cse_session_var_guard.groovy
new file mode 100644
index 00000000000..2862155be51
--- /dev/null
+++ b/regression-test/suites/nereids_p0/test_cse_session_var_guard.groovy
@@ -0,0 +1,77 @@
+// 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("test_cse_session_var_guard") {
+    sql "DROP TABLE IF EXISTS cse_guard_demo"
+    sql """CREATE TABLE cse_guard_demo (id INT, platform VARCHAR(65533))
+        DISTRIBUTED BY HASH(id) BUCKETS 1 PROPERTIES("replication_num" = 
"1")"""
+    sql """INSERT INTO cse_guard_demo VALUES
+        (1, 'abc'), (2, ''), (3, NULL), (4, 'xyz'), (5, 'abc'), (6, ' '), (7, 
'中文')"""
+    sql "DROP FUNCTION IF EXISTS cse_guard_replace_null(VARCHAR(65533))"
+    sql "SET enable_decimal256 = false"
+    sql """CREATE ALIAS FUNCTION cse_guard_replace_null(VARCHAR(65533))
+        WITH PARAMETER(foo) AS IF(foo = '', NULL, foo)"""
+
+    // Evaluate the original projection without guards as a result baseline.
+    qt_without_guard """SELECT id, coalesce(platform, '') AS x, 
coalesce(platform, '') AS y,
+        cse_guard_replace_null(coalesce(platform, '')) AS z
+        FROM cse_guard_demo ORDER BY id"""
+
+    // The function was created with decimal256 disabled. Changing it adds 
guards
+    // to the expanded IF and COALESCE expressions, even for VARCHAR arguments.
+    sql "SET enable_decimal256 = true"
+    sql """EXPLAIN SELECT coalesce(platform, '') AS x, coalesce(platform, '') 
AS y,
+        cse_guard_replace_null(coalesce(platform, '')) AS z FROM 
cse_guard_demo"""
+    qt_with_guard """SELECT id, coalesce(platform, '') AS x, 
coalesce(platform, '') AS y,
+        cse_guard_replace_null(coalesce(platform, '')) AS z
+        FROM cse_guard_demo ORDER BY id"""
+
+    // Reverse discovery order: guarded and unguarded roots still cannot depend
+    // on aliases produced in their own projection layer.
+    qt_guard_first """SELECT id, cse_guard_replace_null(coalesce(platform, 
'')) AS z,
+        coalesce(platform, '') AS x, coalesce(platform, '') AS y
+        FROM cse_guard_demo ORDER BY id"""
+
+    // Reusing an entire guarded expression must remain supported.
+    qt_repeated_guard """SELECT id,
+        cse_guard_replace_null(coalesce(platform, '')) AS z1,
+        cse_guard_replace_null(coalesce(platform, '')) AS z2,
+        coalesce(platform, '') AS x, coalesce(platform, '') AS y
+        FROM cse_guard_demo ORDER BY id"""
+
+    // Common expressions below the guarded root may still be extracted into
+    // earlier layers without separating a guarded root from its guard.
+    qt_nested_guard """SELECT id,
+        coalesce(nullif(platform, 'abc'), 'fallback') AS x,
+        coalesce(nullif(platform, 'abc'), 'fallback') AS y,
+        cse_guard_replace_null(coalesce(nullif(platform, 'abc'), 'fallback')) 
AS z
+        FROM cse_guard_demo ORDER BY id"""
+
+    // Ordinary CSE must retain reuse across multiple projection layers.
+    qt_multiple_layers """SELECT id, id + 1 AS x,
+        (id + 1) * 2 AS y, (id + 1) * 2 + (id + 1) AS z
+        FROM cse_guard_demo ORDER BY id"""
+
+    // Also cover a function created with decimal256 enabled and queried with 
it disabled.
+    sql "DROP FUNCTION IF EXISTS cse_guard_replace_null(VARCHAR(65533))"
+    sql """CREATE ALIAS FUNCTION cse_guard_replace_null(VARCHAR(65533))
+        WITH PARAMETER(foo) AS IF(foo = '', NULL, foo)"""
+    sql "SET enable_decimal256 = false"
+    qt_reverse_session_guard """SELECT id, coalesce(platform, '') AS x, 
coalesce(platform, '') AS y,
+        cse_guard_replace_null(coalesce(platform, '')) AS z
+        FROM cse_guard_demo ORDER BY id"""
+}


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

Reply via email to