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

morrySnow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


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

commit d1cefa974a76e14d4d6dcaaa42c53cf53d5f34d5
Author: feiniaofeiafei <[email protected]>
AuthorDate: Fri Sep 11 17:05:38 2026 +0800

    [fix](SVGuard) Preserve session guards during common expression extraction 
(#67717)
    
    ### What problem does this PR solve?
    
    Related PR: #58031
    
    Problem Summary: Repeated guarded and unguarded expressions in a
    projection can produce aliases that reference outputs defined in the
    same projection layer. Keep a session guard and its wrapped root
    together during replacement, and expose newly created aliases only after
    the entire layer is rewritten. Add unit coverage for guard boundaries
    and layer inputs, and a regression case for alias functions queried with
    different session settings.
---
 .../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 1d5ac1dc24a..25c30a9b7ad 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,19 +24,27 @@ 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.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;
@@ -107,6 +115,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