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

yujun777 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 3b8b5d8f54c [fix](nereids) Allow sync MV rewrite across strict-table 
SQL modes (#69212)
3b8b5d8f54c is described below

commit 3b8b5d8f54ce69076f83e343d3838bbbf5c613d2
Author: Aranya Singh Chauhan <[email protected]>
AuthorDate: Wed Oct 21 08:37:58 2026 +0530

    [fix](nereids) Allow sync MV rewrite across strict-table SQL modes (#69212)
    
    Nereids can fail to rewrite an aggregation query using a compatible
    synchronous materialized-view rollup when the querying session and the
    rollup creation session differ only in the `STRICT_TRANS_TABLES` and/or
    `STRICT_ALL_TABLES` SQL-mode flags.
    
    For example:
    
    - Rollup creation session: `ONLY_FULL_GROUP_BY`
    - Query execution session:
    `ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,STRICT_ALL_TABLES`
    
    Neither strict-table flag currently affects the SQL expression semantics
    relevant to synchronous MV rewriting. However, the existing
    session-variable compatibility check treats these configurations as
    different.
    
    Consequently, an otherwise eligible synchronous rollup may fail
    materialized-view rewriting, causing the optimizer to miss a potentially
    more efficient execution plan.
    
    #### Root Cause
    
    The issue originates from the interaction between persisted session
    variables and session-variable guards in the Nereids materialized-view
    rewrite pipeline.
    
    1. `sql_mode` is marked as a result-affecting session variable and is
    persisted as part of the synchronous materialized view's creation
    metadata.
    2. `SessionVarGuardRewriter.checkSessionVariablesMatch()` performs an
    exact comparison of the current and persisted session-variable maps.
    3. Differences in either strict-table SQL-mode bit cause this comparison
    to return `false`.
    4. `InitMaterializationContextHook.createSyncMvContexts()` uses this
    compatibility result when constructing the synchronous materialization
    context.
    5. When the session variables are considered incompatible, expressions
    implementing `NeedSessionVarGuard`, including `SUM`, may be wrapped in
    `SessionVarGuardExpr`.
    6. The additional expression wrapper can prevent the aggregate-rollup
    matcher from recognizing an otherwise compatible aggregation expression.
    
    This results in a materialized-view rewrite failure caused by SQL-mode
    differences that do not affect the relevant query semantics.
    
    #### Solution
    
    This PR introduces a synchronous-MV-specific session-variable
    compatibility check:
    
    `SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv()`
    
    The new comparison:
    
    - Allows differences exclusively in `STRICT_TRANS_TABLES` and/or
    `STRICT_ALL_TABLES`.
    - Preserves strict comparison of all other SQL-mode bits.
    - Preserves strict comparison of other result-affecting session
    variables.
    - Rejects incompatible or missing session-variable entries.
    - Does not relax compatibility when differing SQL-mode values cannot be
    parsed.
    - Preserves existing behavior for older materialized views without
    persisted session-variable metadata.
    
    The implementation compares SQL modes using their persisted numeric
    bitmask representations, excluding only:
    
    - `SqlModeHelper.MODE_STRICT_TRANS_TABLES`
    - `SqlModeHelper.MODE_STRICT_ALL_TABLES`
    
    Both flags are treated symmetrically because neither currently affects
    the SQL expression semantics used for synchronous MV rewriting.
    
    The change is restricted to synchronous materialized-view context
    initialization in `InitMaterializationContextHook`.
    
    The existing general-purpose `checkSessionVariablesMatch()` method
    remains unchanged.
    
    #### Expected Behavior
    
    For an otherwise compatible synchronous aggregate rollup:
    
    | Rollup creation SQL mode | Query SQL mode | Expected rewrite behavior
    |
    | --- | --- | --- |
    | `ONLY_FULL_GROUP_BY` | `ONLY_FULL_GROUP_BY` | Eligible |
    | `ONLY_FULL_GROUP_BY` | `ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES` |
    Eligible |
    | `ONLY_FULL_GROUP_BY` | `ONLY_FULL_GROUP_BY,STRICT_ALL_TABLES` |
    Eligible |
    | `ONLY_FULL_GROUP_BY` |
    `ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,STRICT_ALL_TABLES` | Eligible |
    | `ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,STRICT_ALL_TABLES` |
    `ONLY_FULL_GROUP_BY` | Eligible |
    | `ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES` |
    `ONLY_FULL_GROUP_BY,STRICT_ALL_TABLES` | Eligible |
    | `ONLY_FULL_GROUP_BY` | `ONLY_FULL_GROUP_BY,PIPES_AS_CONCAT` |
    Incompatible |
    | `ONLY_FULL_GROUP_BY` |
    `ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,PIPES_AS_CONCAT`
    | Incompatible |
    
    Other result-affecting session-variable differences, such as changes to
    `enable_decimal256`, continue to require the existing compatibility
    protections.
    
    #### Regression Test
    
    Regression test:
    
    
    
`regression-test/suites/query_p0/variables_persist/test_sync_rollup_strict_sql_mode.groovy`
    
    The test:
    
    1. Creates an Aggregate Key table with `region_id`, `store_id`, and
    `sales`.
    2. Creates a synchronous aggregate rollup under `ONLY_FULL_GROUP_BY`.
    3. Waits for the rollup creation job to finish successfully.
    4. Validates rollup rewrite eligibility using `EXPLAIN VERBOSE` under
    four SQL-mode configurations:
       - `ONLY_FULL_GROUP_BY`
       - `ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES`
       - `ONLY_FULL_GROUP_BY,STRICT_ALL_TABLES`
       - `ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,STRICT_ALL_TABLES`
    5. Verifies identical aggregation results across all four
    configurations.
    
    The query used for validation is:
    
    ```sql
    SELECT region_id, SUM(sales)
    FROM test_sync_rollup_strict_sql_mode
    GROUP BY region_id
    ORDER BY region_id;
    ```
    
    Expected results:
    
    | region_id | SUM(sales) |
    | --- | --- |
    | 1 | 300 |
    | 2 | 700 |
    
    The regression validates successful rewrite eligibility independently of
    whether the cost-based optimizer ultimately selects the rollup as the
    final physical execution plan.
    
    ---------
    
    Co-authored-by: Aranya Singh Chauhan 
<[email protected]>
---
 .../rules/analysis/SessionVarGuardRewriter.java    |  72 +++++++
 .../mv/InitMaterializationContextHook.java         |   2 +-
 .../trees/expressions/VariablePersistTest.java     | 211 ++++++++++++++++++-
 .../test_sync_rollup_strict_sql_mode.out           |  16 ++
 .../test_sync_rollup_strict_sql_mode.groovy        | 224 +++++++++++++++++++++
 5 files changed, 523 insertions(+), 2 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/SessionVarGuardRewriter.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/SessionVarGuardRewriter.java
index a10cd23719a..463f12702c9 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/SessionVarGuardRewriter.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/SessionVarGuardRewriter.java
@@ -32,11 +32,14 @@ import 
org.apache.doris.nereids.trees.expressions.SessionVarGuardExpr;
 import 
org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionRewriter;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.visitor.DefaultPlanRewriter;
+import org.apache.doris.qe.SessionVariable;
+import org.apache.doris.qe.SqlModeHelper;
 
 import com.google.common.collect.ImmutableList;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * The purpose of this class is to add session var guards to all expressions 
that require guarding
@@ -146,4 +149,73 @@ public class SessionVarGuardRewriter extends 
ExpressionRewrite {
         }
         return currentSessionVars.equals(persistSessionVars);
     }
+
+    /**
+     * Check session-variable compatibility for synchronous materialized views.
+     *
+     * Differences only in STRICT_TRANS_TABLES and STRICT_ALL_TABLES do not
+     * affect the SQL expressions used for synchronous MV rewriting.
+     *
+     * Other SQL-mode bits and result-affecting session variables
+     * must still match.
+     */
+    public static boolean checkSessionVariablesMatchForSyncMv(
+            Map<String, String> currentSessionVars,
+            Map<String, String> persistSessionVars) {
+
+        // Preserve compatibility with older MVs that have no saved session 
variables.
+        if (persistSessionVars == null || persistSessionVars.isEmpty()) {
+            return true;
+        }
+
+        // A nonempty persisted configuration cannot match missing current 
state.
+        if (currentSessionVars == null) {
+            return false;
+        }
+
+        // Retain the existing exact-match behavior whenever possible.
+        if (checkSessionVariablesMatch(currentSessionVars, 
persistSessionVars)) {
+            return true;
+        }
+
+        // Every result-affecting session variable must still be present.
+        if (!currentSessionVars.keySet().equals(persistSessionVars.keySet())) {
+            return false;
+        }
+
+        for (Map.Entry<String, String> entry : currentSessionVars.entrySet()) {
+            String name = entry.getKey();
+            String currentValue = entry.getValue();
+            String persistedValue = persistSessionVars.get(name);
+
+            if (Objects.equals(currentValue, persistedValue)) {
+                continue;
+            }
+
+            // Only sql_mode is eligible for the narrowly scoped exception.
+            if (!SessionVariable.SQL_MODE.equals(name)) {
+                return false;
+            }
+
+            try {
+                long currentMode = Long.parseLong(currentValue);
+                long persistedMode = Long.parseLong(persistedValue);
+
+                // Neither strict-table SQL-mode bit affects synchronous MV 
rewrite semantics.
+                long ignoredModes = SqlModeHelper.MODE_STRICT_TRANS_TABLES
+                        | SqlModeHelper.MODE_STRICT_ALL_TABLES;
+                long relevantModes = ~ignoredModes;
+
+                if ((currentMode & relevantModes)
+                        != (persistedMode & relevantModes)) {
+                    return false;
+                }
+            } catch (NumberFormatException e) {
+                // Malformed, missing, or nonnumeric SQL modes cannot use the 
exception.
+                return false;
+            }
+        }
+
+        return true;
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/InitMaterializationContextHook.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/InitMaterializationContextHook.java
index 2852fdd2a68..c1642d6f433 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/InitMaterializationContextHook.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/InitMaterializationContextHook.java
@@ -285,7 +285,7 @@ public class InitMaterializationContextHook implements 
PlannerHook {
                                 
MTMVPlanUtil.DISABLE_RULES_WHEN_GENERATE_MTMV_CACHE, 
meta.getSessionVariables());
                         basicMvContext.setDatabase(meta.getDbName());
 
-                        boolean sessionVarMatch = 
SessionVarGuardRewriter.checkSessionVariablesMatch(
+                        boolean sessionVarMatch = 
SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(
                                 
ConnectContextUtil.getAffectQueryResultInPlanVariables(
                                         cascadesContext.getConnectContext()), 
meta.getSessionVariables());
                         MTMVCache mtmvCache = MTMVCache.from(querySql.get(),
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/VariablePersistTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/VariablePersistTest.java
index d2752e58964..38724d5fdba 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/VariablePersistTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/VariablePersistTest.java
@@ -32,6 +32,8 @@ import org.apache.doris.nereids.types.DecimalV3Type;
 import org.apache.doris.nereids.types.MapType;
 import org.apache.doris.nereids.types.StringType;
 import org.apache.doris.nereids.util.MemoTestUtils;
+import org.apache.doris.qe.SessionVariable;
+import org.apache.doris.qe.SqlModeHelper;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -199,7 +201,6 @@ public class VariablePersistTest extends 
ExpressionRewriteTestHelper {
         Assertions.assertEquals(rewritten, new And(slotA, slotB));
     }
 
-
     // guard1(guard2(child)) -> exception
     @Test
     public void testMergeGuardExprException() {
@@ -232,4 +233,212 @@ public class VariablePersistTest extends 
ExpressionRewriteTestHelper {
         Assertions.assertEquals(child, resultGuard.child());
         Assertions.assertEquals(sessionVars, resultGuard.getSessionVars());
     }
+
+    private static Map<String, String> syncMvSessionVars(long sqlMode, boolean 
enableDecimal256) {
+        return ImmutableMap.of(
+                SessionVariable.SQL_MODE, Long.toString(sqlMode),
+                "enable_decimal256", Boolean.toString(enableDecimal256));
+    }
+
+    @Test
+    public void testSyncMvMatchingSqlModes() {
+        long mode = SqlModeHelper.MODE_ONLY_FULL_GROUP_BY;
+        Map<String, String> current = syncMvSessionVars(mode, false);
+        Map<String, String> persisted = syncMvSessionVars(mode, false);
+
+        Assertions.assertTrue(
+                
SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(current, 
persisted));
+    }
+
+    @Test
+    public void testSyncMvStrictTransTablesDifferenceInBothDirections() {
+        long baseMode = SqlModeHelper.MODE_ONLY_FULL_GROUP_BY;
+        long strictMode = baseMode | SqlModeHelper.MODE_STRICT_TRANS_TABLES;
+        Map<String, String> withoutStrict = syncMvSessionVars(baseMode, false);
+        Map<String, String> withStrict = syncMvSessionVars(strictMode, false);
+
+        // Rollup created without strict mode, queried with strict mode.
+        Assertions.assertTrue(
+                
SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(withStrict, 
withoutStrict));
+
+        // Rollup created with strict mode, queried without strict mode.
+        Assertions.assertTrue(
+                
SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(withoutStrict, 
withStrict));
+
+        // Identical strict-mode configurations must also match.
+        Assertions.assertTrue(
+                
SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(withStrict, 
withStrict));
+
+        // Compatibility also applies when SQL_MODE is the only persisted 
variable.
+        Map<String, String> persistedSqlModeOnly =
+                ImmutableMap.of(SessionVariable.SQL_MODE, 
Long.toString(baseMode));
+        Map<String, String> currentSqlModeOnly =
+                ImmutableMap.of(SessionVariable.SQL_MODE, 
Long.toString(strictMode));
+        Assertions.assertTrue(
+                SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(
+                        currentSqlModeOnly, persistedSqlModeOnly));
+    }
+
+    @Test
+    public void testSyncMvOtherSqlModeDifferencesRemainIncompatible() {
+        long baseMode = SqlModeHelper.MODE_ONLY_FULL_GROUP_BY;
+        Map<String, String> persisted = syncMvSessionVars(baseMode, false);
+        long[] otherModes = {
+                SqlModeHelper.MODE_PIPES_AS_CONCAT,
+                SqlModeHelper.MODE_NO_BACKSLASH_ESCAPES
+        };
+
+        for (long otherMode : otherModes) {
+            Map<String, String> current = syncMvSessionVars(baseMode | 
otherMode, false);
+            Assertions.assertFalse(
+                    
SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(current, persisted),
+                    "Unexpectedly accepted SQL-mode bit: " + otherMode);
+        }
+
+        // The exception must not allow other mode changes with SQL_MODE-only 
state, either.
+        Map<String, String> persistedSqlModeOnly =
+                ImmutableMap.of(SessionVariable.SQL_MODE, 
Long.toString(baseMode));
+        Map<String, String> currentSqlModeOnly = ImmutableMap.of(
+                SessionVariable.SQL_MODE,
+                Long.toString(baseMode | SqlModeHelper.MODE_PIPES_AS_CONCAT));
+        Assertions.assertFalse(
+                SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(
+                        currentSqlModeOnly, persistedSqlModeOnly));
+    }
+
+    @Test
+    public void testSyncMvStrictTableModesDoNotHideOtherModeDifferences() {
+        long baseMode = SqlModeHelper.MODE_ONLY_FULL_GROUP_BY;
+        long strictModes = SqlModeHelper.MODE_STRICT_TRANS_TABLES
+                | SqlModeHelper.MODE_STRICT_ALL_TABLES;
+
+        Map<String, String> persisted = syncMvSessionVars(baseMode, false);
+
+        long[] incompatibleModes = {
+            SqlModeHelper.MODE_PIPES_AS_CONCAT,
+            SqlModeHelper.MODE_NO_BACKSLASH_ESCAPES
+        };
+
+        for (long incompatibleMode : incompatibleModes) {
+            Map<String, String> current = syncMvSessionVars(
+                    baseMode | strictModes | incompatibleMode, false);
+
+            Assertions.assertFalse(
+                    
SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(
+                    current, persisted),
+                    "Strict-table exemption must not hide other SQL-mode 
differences: "
+                    + incompatibleMode);
+        }
+    }
+
+    @Test
+    public void testSyncMvOtherSessionVariablesStillRequireExactMatch() {
+        long baseMode = SqlModeHelper.MODE_ONLY_FULL_GROUP_BY;
+        long strictMode = baseMode
+                | SqlModeHelper.MODE_STRICT_TRANS_TABLES
+                | SqlModeHelper.MODE_STRICT_ALL_TABLES;
+        Map<String, String> persisted = syncMvSessionVars(baseMode, false);
+
+        // A strict-mode difference is allowed, but an unrelated variable 
mismatch is not.
+        Map<String, String> current = syncMvSessionVars(strictMode, true);
+        Assertions.assertFalse(
+                
SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(current, 
persisted));
+    }
+
+    @Test
+    public void testSyncMvMissingSessionVariablesRemainIncompatible() {
+        long baseMode = SqlModeHelper.MODE_ONLY_FULL_GROUP_BY;
+        Map<String, String> persisted = syncMvSessionVars(baseMode, false);
+        Map<String, String> missingSqlMode = 
ImmutableMap.of("enable_decimal256", "false");
+        Map<String, String> missingDecimal256 =
+                ImmutableMap.of(SessionVariable.SQL_MODE, 
Long.toString(baseMode));
+
+        Assertions.assertFalse(
+                
SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(missingSqlMode, 
persisted));
+        Assertions.assertFalse(
+                
SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(missingDecimal256, 
persisted));
+        Assertions.assertFalse(
+                
SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(null, persisted));
+    }
+
+    @Test
+    public void testSyncMvMalformedSqlModeDoesNotBypassGuard() {
+        long baseMode = SqlModeHelper.MODE_ONLY_FULL_GROUP_BY;
+        Map<String, String> persisted = syncMvSessionVars(baseMode, false);
+        Map<String, String> malformed = ImmutableMap.of(
+                SessionVariable.SQL_MODE, "INVALID_SQL_MODE",
+                "enable_decimal256", "false");
+
+        Assertions.assertFalse(
+                
SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(malformed, 
persisted));
+        Assertions.assertFalse(
+                
SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(persisted, 
malformed));
+    }
+
+    @Test
+    public void testSyncMvPreservesEmptyPersistedVariableBehavior() {
+        Map<String, String> current = syncMvSessionVars(
+                SqlModeHelper.MODE_ONLY_FULL_GROUP_BY, false);
+
+        // Preserve the existing behavior for older MVs without saved 
variables.
+        Assertions.assertTrue(
+                
SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(current, null));
+        Assertions.assertTrue(
+                
SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(current, 
ImmutableMap.of()));
+    }
+
+    @Test
+    public void testGeneralSessionVariableComparisonRemainsStrict() {
+        long baseMode = SqlModeHelper.MODE_ONLY_FULL_GROUP_BY;
+        long strictMode = baseMode
+                | SqlModeHelper.MODE_STRICT_TRANS_TABLES
+                | SqlModeHelper.MODE_STRICT_ALL_TABLES;
+        Map<String, String> persisted = syncMvSessionVars(baseMode, false);
+        Map<String, String> current = syncMvSessionVars(strictMode, false);
+
+        // The Sync MV exception must not weaken the general-purpose 
comparator.
+        Assertions.assertFalse(
+                SessionVarGuardRewriter.checkSessionVariablesMatch(current, 
persisted));
+        Assertions.assertTrue(
+                
SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(current, 
persisted));
+    }
+
+    @Test
+    public void testSyncMvStrictTableModeCombinations() {
+        long baseMode = SqlModeHelper.MODE_ONLY_FULL_GROUP_BY;
+        long strictTrans = SqlModeHelper.MODE_STRICT_TRANS_TABLES;
+        long strictAll = SqlModeHelper.MODE_STRICT_ALL_TABLES;
+
+        long[] modes = {
+            baseMode,
+            baseMode | strictTrans,
+            baseMode | strictAll,
+            baseMode | strictTrans | strictAll
+        };
+
+        // All four configurations should be mutually compatible for sync MVs.
+        for (long persistedMode : modes) {
+            for (long currentMode : modes) {
+                Assertions.assertTrue(
+                        
SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(
+                        syncMvSessionVars(currentMode, false),
+                        syncMvSessionVars(persistedMode, false)),
+                        "Strict-table SQL-mode flags should not block sync MV 
rewriting: "
+                        + "current=" + currentMode + ", persisted=" + 
persistedMode);
+            }
+        }
+
+        // The same exemption applies when SQL_MODE is the only saved variable.
+        Map<String, String> baseSqlModeOnly = ImmutableMap.of(
+                SessionVariable.SQL_MODE, Long.toString(baseMode));
+        Map<String, String> bothStrictSqlModeOnly = ImmutableMap.of(
+                SessionVariable.SQL_MODE, Long.toString(baseMode | strictTrans 
| strictAll));
+
+        Assertions.assertTrue(
+                SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(
+                bothStrictSqlModeOnly, baseSqlModeOnly));
+        Assertions.assertTrue(
+                SessionVarGuardRewriter.checkSessionVariablesMatchForSyncMv(
+                baseSqlModeOnly, bothStrictSqlModeOnly));
+    }
 }
diff --git 
a/regression-test/data/query_p0/variables_persist/test_sync_rollup_strict_sql_mode.out
 
b/regression-test/data/query_p0/variables_persist/test_sync_rollup_strict_sql_mode.out
new file mode 100644
index 00000000000..7d557551ccc
--- /dev/null
+++ 
b/regression-test/data/query_p0/variables_persist/test_sync_rollup_strict_sql_mode.out
@@ -0,0 +1,16 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !normal_sql_mode --
+1      300
+2      700
+
+-- !strict_sql_mode --
+1      300
+2      700
+
+-- !strict_all_sql_mode --
+1      300
+2      700
+
+-- !both_strict_sql_modes --
+1      300
+2      700
diff --git 
a/regression-test/suites/query_p0/variables_persist/test_sync_rollup_strict_sql_mode.groovy
 
b/regression-test/suites/query_p0/variables_persist/test_sync_rollup_strict_sql_mode.groovy
new file mode 100644
index 00000000000..0e2c25dc5cb
--- /dev/null
+++ 
b/regression-test/suites/query_p0/variables_persist/test_sync_rollup_strict_sql_mode.groovy
@@ -0,0 +1,224 @@
+// 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_sync_rollup_strict_sql_mode", "p0") {
+
+    def rollupName = "rollup_by_region"
+
+    def query = """
+        SELECT region_id, SUM(sales)
+        FROM test_sync_rollup_strict_sql_mode
+        GROUP BY region_id
+        ORDER BY region_id
+    """
+
+    // Preserve session settings modified by this test.
+    def originalSqlMode = sql("SELECT @@sql_mode")[0][0].toString()
+    def originalNereids = sql("SELECT @@enable_nereids_planner")[0][0]
+    def originalFallback = sql("SELECT 
@@enable_fallback_to_original_planner")[0][0]
+    def originalSqlCache = sql("SELECT @@enable_sql_cache")[0][0]
+
+    try {
+        sql "SET enable_nereids_planner = true"
+        sql "SET enable_fallback_to_original_planner = false"
+        sql "SET enable_sql_cache = false"
+
+        // Create the synchronous rollup without either strict-table SQL-mode 
bit.
+        sql "SET sql_mode = 'ONLY_FULL_GROUP_BY'"
+
+        sql "DROP TABLE IF EXISTS test_sync_rollup_strict_sql_mode"
+
+        sql """
+            CREATE TABLE test_sync_rollup_strict_sql_mode (
+                region_id INT NOT NULL,
+                store_id INT NOT NULL,
+                sales BIGINT SUM NOT NULL
+            )
+            AGGREGATE KEY(region_id, store_id)
+            DISTRIBUTED BY HASH(region_id) BUCKETS 1
+            PROPERTIES ("replication_num" = "1")
+        """
+
+        sql """
+            INSERT INTO test_sync_rollup_strict_sql_mode VALUES
+                (1, 101, 100),
+                (1, 102, 200),
+                (2, 201, 300),
+                (2, 202, 400)
+        """
+
+        sql """
+            ALTER TABLE test_sync_rollup_strict_sql_mode
+            ADD ROLLUP ${rollupName} (
+                region_id,
+                sales
+            )
+        """
+
+        // Rollup creation is asynchronous. Wait until it is usable.
+        boolean rollupFinished = false
+
+        for (int attempt = 0; attempt < 90; attempt++) {
+            def jobs = sql """
+                SHOW ALTER TABLE ROLLUP
+                WHERE TableName = 'test_sync_rollup_strict_sql_mode'
+                ORDER BY CreateTime DESC
+                LIMIT 1
+            """
+
+            if (!jobs.isEmpty()) {
+                String state = jobs[0][8].toString()
+
+                if (state == "FINISHED") {
+                    rollupFinished = true
+                    break
+                }
+
+                assertFalse(
+                    state == "CANCELLED",
+                    "Rollup creation was cancelled: ${jobs[0]}"
+                )
+            }
+
+            Thread.sleep(2000)
+        }
+
+        assertTrue(
+            rollupFinished,
+            "Rollup ${rollupName} did not finish creating"
+        )
+
+        // Baseline: matching SQL modes must allow rollup rewriting.
+        explain {
+            sql query
+            verbose true
+
+            check { plan ->
+                boolean rewriteSucceeded =
+                    plan.contains("${rollupName} chose")
+                        || plan.contains("${rollupName} not chose")
+
+                assertTrue(
+                    rewriteSucceeded,
+                    "Rollup rewrite failed with matching SQL mode:\n${plan}"
+                )
+
+                assertFalse(
+                    plan.contains("${rollupName} fail"),
+                    "Unexpected rollup rewrite failure:\n${plan}"
+                )
+
+                return true
+            }
+        }
+
+        qt_normal_sql_mode(query)
+
+        // Regression: STRICT_TRANS_TABLES alone must not
+        // invalidate an otherwise compatible synchronous rollup.
+        sql "SET sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES'"
+
+        explain {
+            sql query
+            verbose true
+
+            check { plan ->
+                boolean rewriteSucceeded =
+                    plan.contains("${rollupName} chose")
+                        || plan.contains("${rollupName} not chose")
+
+                assertTrue(
+                    rewriteSucceeded,
+                    "STRICT_TRANS_TABLES prevented rollup rewriting:\n${plan}"
+                )
+
+                assertFalse(
+                    plan.contains("${rollupName} fail"),
+                    "Unexpected rollup rewrite failure:\n${plan}"
+                )
+
+                return true
+            }
+        }
+
+        qt_strict_sql_mode(query)
+
+        // STRICT_ALL_TABLES alone must not prevent compatible rollup 
rewriting.
+        sql "SET sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_ALL_TABLES'"
+
+        explain {
+            sql query
+            verbose true
+
+            check { plan ->
+                boolean rewriteSucceeded =
+                    plan.contains("${rollupName} chose")
+                        || plan.contains("${rollupName} not chose")
+
+                assertTrue(
+                    rewriteSucceeded,
+                    "STRICT_ALL_TABLES prevented rollup rewriting:\n${plan}"
+                )
+
+                assertFalse(
+                    plan.contains("${rollupName} fail"),
+                    "Unexpected rollup rewrite failure:\n${plan}"
+                )
+
+                return true
+            }
+        }
+
+        qt_strict_all_sql_mode(query)
+
+        // Both strict-table SQL-mode bits together must also remain 
compatible.
+        sql "SET sql_mode = 
'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,STRICT_ALL_TABLES'"
+
+        explain {
+            sql query
+            verbose true
+
+            check { plan ->
+                boolean rewriteSucceeded =
+                    plan.contains("${rollupName} chose")
+                        || plan.contains("${rollupName} not chose")
+
+                assertTrue(
+                    rewriteSucceeded,
+                    "Both strict-table bits prevented rollup 
rewriting:\n${plan}"
+                )
+
+                assertFalse(
+                    plan.contains("${rollupName} fail"),
+                    "Unexpected rollup rewrite failure:\n${plan}"
+                )
+
+                return true
+            }
+        }
+
+        qt_both_strict_sql_modes(query)
+
+    } finally {
+
+        // Restore the original session settings.
+        sql "SET sql_mode = '${originalSqlMode}'"
+        sql "SET enable_sql_cache = ${originalSqlCache}"
+        sql "SET enable_fallback_to_original_planner = ${originalFallback}"
+        sql "SET enable_nereids_planner = ${originalNereids}"
+    }
+}


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

Reply via email to