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 8989e23cb9c branch-4.0: [Fix](nereids) Fix session variable not take 
effect for partial update in multi-statement batch #60803 (#60862)
8989e23cb9c is described below

commit 8989e23cb9c373db1711d2bab269f51423d5267e
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sat Feb 28 11:19:55 2026 +0800

    branch-4.0: [Fix](nereids) Fix session variable not take effect for partial 
update in multi-statement batch #60803 (#60862)
    
    Cherry-picked from #60803
    
    Co-authored-by: bobhan1 <[email protected]>
---
 .../doris/nereids/analyzer/UnboundTableSink.java   |   6 +-
 .../trees/plans/commands/insert/InsertUtils.java   |  13 ++
 .../test_partial_update_multi_stmt.out             |  25 +++
 .../test_partial_update_multi_stmt.groovy          | 168 +++++++++++++++++++++
 4 files changed, 211 insertions(+), 1 deletion(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundTableSink.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundTableSink.java
index b9290c796c2..ed04ad57372 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundTableSink.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundTableSink.java
@@ -50,7 +50,7 @@ public class UnboundTableSink<CHILD_TYPE extends Plan> 
extends UnboundLogicalSin
     private final boolean temporaryPartition;
     private final List<String> partitions;
     private boolean isPartialUpdate;
-    private final TPartialUpdateNewRowPolicy partialUpdateNewKeyPolicy;
+    private TPartialUpdateNewRowPolicy partialUpdateNewKeyPolicy;
     private final DMLCommandType dmlCommandType;
     private final boolean autoDetectPartition;
 
@@ -126,6 +126,10 @@ public class UnboundTableSink<CHILD_TYPE extends Plan> 
extends UnboundLogicalSin
         this.isPartialUpdate = isPartialUpdate;
     }
 
+    public void setPartialUpdateNewKeyPolicy(TPartialUpdateNewRowPolicy 
policy) {
+        this.partialUpdateNewKeyPolicy = policy;
+    }
+
     @Override
     public Plan withChildren(List<Plan> children) {
         Preconditions.checkArgument(children.size() == 1, 
"UnboundOlapTableSink only accepts one child");
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertUtils.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertUtils.java
index 1cfef22a10e..b03b248b651 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertUtils.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertUtils.java
@@ -293,6 +293,19 @@ public class InsertUtils {
             // For JDBC External Table, we always allow certain columns to be 
missing during insertion
             // Specific check for non-nullable columns only if insertion is 
direct VALUES or SELECT constants
         }
+        // Re-read partial update settings from session variable to handle 
multi-statement
+        // batches where SET and INSERT are parsed together before execution.
+        // Only apply to original INSERT statements, not DELETE/UPDATE 
converted to INSERT.
+        if (unboundLogicalSink instanceof UnboundTableSink
+                && unboundLogicalSink.getDMLCommandType() == 
DMLCommandType.INSERT) {
+            ConnectContext ctx = ConnectContext.get();
+            if (ctx != null) {
+                ((UnboundTableSink<? extends Plan>) unboundLogicalSink)
+                        
.setPartialUpdate(ctx.getSessionVariable().isEnableUniqueKeyPartialUpdate());
+                ((UnboundTableSink<? extends Plan>) unboundLogicalSink)
+                        
.setPartialUpdateNewKeyPolicy(ctx.getSessionVariable().getPartialUpdateNewRowPolicy());
+            }
+        }
         if (table instanceof OlapTable && ((OlapTable) table).getKeysType() == 
KeysType.UNIQUE_KEYS) {
             if (unboundLogicalSink instanceof UnboundTableSink
                     && ((UnboundTableSink<? extends Plan>) 
unboundLogicalSink).isPartialUpdate()) {
diff --git 
a/regression-test/data/unique_with_mow_p0/partial_update/test_partial_update_multi_stmt.out
 
b/regression-test/data/unique_with_mow_p0/partial_update/test_partial_update_multi_stmt.out
new file mode 100644
index 00000000000..546b0cf2c4a
--- /dev/null
+++ 
b/regression-test/data/unique_with_mow_p0/partial_update/test_partial_update_multi_stmt.out
@@ -0,0 +1,25 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !base1 --
+1      1       1       1
+2      2       2       2
+
+-- !base2 --
+1      1       1       1
+2      2       2       2
+
+-- !multi_stmt_values --
+1      100     1       1
+2      200     2       2
+
+-- !multi_stmt_select --
+1      501     1       1
+2      502     2       2
+
+-- !separate_stmt_values --
+1      100     1       1
+2      200     2       2
+
+-- !separate_stmt_select --
+1      501     1       1
+2      502     2       2
+
diff --git 
a/regression-test/suites/unique_with_mow_p0/partial_update/test_partial_update_multi_stmt.groovy
 
b/regression-test/suites/unique_with_mow_p0/partial_update/test_partial_update_multi_stmt.groovy
new file mode 100644
index 00000000000..7dbc33b5c43
--- /dev/null
+++ 
b/regression-test/suites/unique_with_mow_p0/partial_update/test_partial_update_multi_stmt.groovy
@@ -0,0 +1,168 @@
+// 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.
+
+import java.sql.Connection
+import java.sql.DriverManager
+import java.sql.Statement
+
+// Test that `set enable_unique_key_partial_update=true; insert into ...` 
works correctly
+// when sent as a single multi-statement query (single COM_QUERY).
+// This is a regression test for the issue where session variables set via SET 
in a
+// multi-statement batch might not be visible during the parse phase of 
subsequent
+// statements in the same batch, because all statements are parsed together 
before
+// any of them are executed.
+suite("test_partial_update_multi_stmt", "p0") {
+
+    String db = context.config.getDbNameByFile(context.file)
+    sql "select 1;" // to create database
+
+    String jdbcUrl = context.config.jdbcUrl
+    String urlWithoutSchema = jdbcUrl.substring(jdbcUrl.indexOf("://") + 3)
+    def sql_ip = urlWithoutSchema.substring(0, urlWithoutSchema.indexOf(":"))
+    def sql_port
+    if (urlWithoutSchema.indexOf("/") >= 0) {
+        sql_port = urlWithoutSchema.substring(urlWithoutSchema.indexOf(":") + 
1, urlWithoutSchema.indexOf("/"))
+    } else {
+        sql_port = urlWithoutSchema.substring(urlWithoutSchema.indexOf(":") + 
1)
+    }
+
+    def tableName1 = "test_partial_update_multi_stmt1"
+    def tableName2 = "test_partial_update_multi_stmt2"
+    def tableName3 = "test_partial_update_multi_stmt3"
+    def tableName4 = "test_partial_update_multi_stmt4"
+
+    def createTable = { tblName ->
+        sql """ DROP TABLE IF EXISTS ${tblName} """
+        sql """
+            CREATE TABLE ${tblName} (
+                `k` int(11) NOT NULL COMMENT "key",
+                `v1` int(11) NULL DEFAULT "10" COMMENT "value1",
+                `v2` int(11) NULL DEFAULT "20" COMMENT "value2",
+                `v3` int(11) NULL DEFAULT "30" COMMENT "value3"
+            ) UNIQUE KEY(`k`)
+            DISTRIBUTED BY HASH(`k`) BUCKETS 1
+            PROPERTIES(
+                "replication_num" = "1",
+                "enable_unique_key_merge_on_write" = "true"
+            ); """
+        sql "insert into ${tblName} values(1, 1, 1, 1), (2, 2, 2, 2);"
+        sql "sync;"
+    }
+
+    // ============================================================
+    // Setup: create all tables with initial data (1,1,1,1),(2,2,2,2)
+    // ============================================================
+    connect(context.config.jdbcUser, context.config.jdbcPassword, 
context.config.jdbcUrl) {
+        sql "use ${db};"
+        sql "sync;"
+        createTable(tableName1)
+        createTable(tableName2)
+        createTable(tableName3)
+        createTable(tableName4)
+        qt_base1 "select * from ${tableName1} order by k;"
+        qt_base2 "select * from ${tableName2} order by k;"
+    }
+
+    // ============================================================
+    // Test 1 & 2: multi-statement with allowMultiQueries=true
+    // SET + INSERT sent as a single COM_QUERY
+    // ============================================================
+    def multiStmtUrl = 
"jdbc:mysql://${sql_ip}:${sql_port}/${db}?useLocalSessionState=false&allowMultiQueries=true"
+    logger.info("multi-statement JDBC URL: ${multiStmtUrl}")
+
+    try (Connection conn = DriverManager.getConnection(multiStmtUrl,
+            context.config.jdbcUser, context.config.jdbcPassword)) {
+        Statement stmt = conn.createStatement()
+
+        // Test 1: SET + INSERT VALUES as single COM_QUERY
+        logger.info("Test 1: allowMultiQueries=true, SET + INSERT VALUES")
+        stmt.execute(
+            "set enable_unique_key_partial_update=true;" +
+            "set enable_insert_strict=false;" +
+            "insert into ${tableName1}(k, v1) values(1, 100), (2, 200);"
+        )
+        stmt.execute("set enable_unique_key_partial_update=false;")
+
+        // Test 2: SET + INSERT SELECT as single COM_QUERY
+        logger.info("Test 2: allowMultiQueries=true, SET + INSERT SELECT")
+        stmt.execute(
+            "set enable_unique_key_partial_update=true;" +
+            "set enable_insert_strict=false;" +
+            "insert into ${tableName2}(k, v1) select k, v1 + 500 from 
${tableName2};"
+        )
+        stmt.execute("set enable_unique_key_partial_update=false;")
+
+        stmt.close()
+    }
+
+    // ============================================================
+    // Test 3 & 4: without allowMultiQueries (statements sent separately)
+    // SET and INSERT are separate COM_QUERY packets
+    // ============================================================
+    def singleStmtUrl = 
"jdbc:mysql://${sql_ip}:${sql_port}/${db}?useLocalSessionState=false"
+    logger.info("single-statement JDBC URL: ${singleStmtUrl}")
+
+    try (Connection conn = DriverManager.getConnection(singleStmtUrl,
+            context.config.jdbcUser, context.config.jdbcPassword)) {
+        Statement stmt = conn.createStatement()
+
+        // Test 3: SET then INSERT VALUES as separate COM_QUERY
+        logger.info("Test 3: separate statements, SET + INSERT VALUES")
+        stmt.execute("set enable_unique_key_partial_update=true;")
+        stmt.execute("set enable_insert_strict=false;")
+        stmt.execute("insert into ${tableName3}(k, v1) values(1, 100), (2, 
200);")
+        stmt.execute("set enable_unique_key_partial_update=false;")
+
+        // Test 4: SET then INSERT SELECT as separate COM_QUERY
+        logger.info("Test 4: separate statements, SET + INSERT SELECT")
+        stmt.execute("set enable_unique_key_partial_update=true;")
+        stmt.execute("set enable_insert_strict=false;")
+        stmt.execute("insert into ${tableName4}(k, v1) select k, v1 + 500 from 
${tableName4};")
+        stmt.execute("set enable_unique_key_partial_update=false;")
+
+        stmt.close()
+    }
+
+    // ============================================================
+    // Verify all results
+    // ============================================================
+    connect(context.config.jdbcUser, context.config.jdbcPassword, 
context.config.jdbcUrl) {
+        sql "use ${db};"
+        sql "sync;"
+
+        // Test 1: allowMultiQueries + INSERT VALUES
+        // Expected (partial update): v2,v3 keep old values (1,1) and (2,2)
+        qt_multi_stmt_values "select * from ${tableName1} order by k;"
+
+        // Test 2: allowMultiQueries + INSERT SELECT
+        // Expected (partial update): v2,v3 keep old values
+        qt_multi_stmt_select "select * from ${tableName2} order by k;"
+
+        // Test 3: separate statements + INSERT VALUES
+        // Expected (partial update): v2,v3 keep old values (1,1) and (2,2)
+        qt_separate_stmt_values "select * from ${tableName3} order by k;"
+
+        // Test 4: separate statements + INSERT SELECT
+        // Expected (partial update): v2,v3 keep old values
+        qt_separate_stmt_select "select * from ${tableName4} order by k;"
+
+        sql """ DROP TABLE IF EXISTS ${tableName1} """
+        sql """ DROP TABLE IF EXISTS ${tableName2} """
+        sql """ DROP TABLE IF EXISTS ${tableName3} """
+        sql """ DROP TABLE IF EXISTS ${tableName4} """
+    }
+}


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

Reply via email to