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

924060929 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 f6d70879307 [fix](load) Reject partial updates in transactional VALUES 
inserts (#67667)
f6d70879307 is described below

commit f6d708793079cca36c0538a07b6093ec318f17e4
Author: bobhan1 <[email protected]>
AuthorDate: Wed Oct 14 15:14:21 2026 +0800

    [fix](load) Reject partial updates in transactional VALUES inserts (#67667)
    
    ### What problem does this PR solve?
    
    Partial updates through `INSERT INTO ... VALUES` in an explicit
    transaction can silently overwrite omitted columns with NULL and
    interpret later statements using the first statement's column mapping.
    For example, updating `(k, v1)` followed by `(k, v2)` can write the
    second statement's value into `v1`.
    
    Reject actual partial updates in `BatchInsertIntoTableCommand` before
    changing transaction state or initializing/appending to the load, and
    remove the now-unreachable partial-column mapping. Full-row VALUES
    inserts remain supported even when the partial-update session variable
    is enabled. Autocommit and transactional INSERT SELECT retain their
    existing behavior. Earlier successful statements remain committable or
    rollbackable after a rejected statement.
    
    ### Release note
    
    Partial updates using INSERT INTO VALUES in explicit transactions now
    fail with a clear error instead of potentially writing incorrect data.
---
 .../insert/BatchInsertIntoTableCommand.java        | 19 ++---
 .../transaction/test_txn_values_partial_update.out | 28 +++++++
 .../org/apache/doris/regression/Config.groovy      |  1 +
 .../test_txn_values_partial_update.groovy          | 87 ++++++++++++++++++++++
 4 files changed, 121 insertions(+), 14 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/BatchInsertIntoTableCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/BatchInsertIntoTableCommand.java
index 55ddf9fb4ec..db6379d7c34 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/BatchInsertIntoTableCommand.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/BatchInsertIntoTableCommand.java
@@ -52,7 +52,6 @@ import org.apache.doris.qe.StmtExecutor;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -141,6 +140,10 @@ public class BatchInsertIntoTableCommand extends Command 
implements NoForward, E
                     
.<TreeNode<?>>collect(PhysicalOlapTableSink.class::isInstance).stream().findAny();
             Preconditions.checkArgument(plan.isPresent(), "insert into command 
must contain OlapTableSinkNode");
             sink = ((PhysicalOlapTableSink<?>) plan.get());
+            if (sink.isPartialUpdate()) {
+                throw new AnalysisException(
+                        "Partial update is not supported for INSERT INTO 
VALUES in an explicit transaction.");
+            }
             Table targetTable = sink.getTargetTable();
             if (ctx.getTxnEntry().isFirstTxnInsert()) {
                 ctx.getTxnEntry().setTxnSchemaVersion(((OlapTable) 
targetTable).getBaseSchemaVersion());
@@ -154,19 +157,7 @@ public class BatchInsertIntoTableCommand extends Command 
implements NoForward, E
             }
             // should set columns of sink since we maybe generate some 
invisible columns
             List<Column> fullSchema = sink.getTargetTable().getFullSchema();
-            List<Column> targetSchema = Lists.newArrayList();
-            if (sink.isPartialUpdate()) {
-                List<String> partialUpdateColumns = sink.getCols().stream()
-                        .map(Column::getName)
-                        .collect(Collectors.toList());
-                for (Column column : fullSchema) {
-                    if (partialUpdateColumns.contains(column.getName())) {
-                        targetSchema.add(column);
-                    }
-                }
-            } else {
-                targetSchema = removeSkipBitmapCol(fullSchema);
-            }
+            List<Column> targetSchema = removeSkipBitmapCol(fullSchema);
             // check auth
             if (!Env.getCurrentEnv().getAccessManager()
                     .checkTblPriv(ConnectContext.get(), 
targetTable.getDatabase().getCatalog().getName(),
diff --git 
a/regression-test/data/insert_p0/transaction/test_txn_values_partial_update.out 
b/regression-test/data/insert_p0/transaction/test_txn_values_partial_update.out
new file mode 100644
index 00000000000..9238a46ea4f
--- /dev/null
+++ 
b/regression-test/data/insert_p0/transaction/test_txn_values_partial_update.out
@@ -0,0 +1,28 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !rejected_first --
+1      100     1000
+2      200     2000
+
+-- !commit_full_rows --
+1      100     1000
+2      200     2000
+3      300     3000
+4      400     4000
+
+-- !rollback --
+1      100     1000
+2      200     2000
+3      300     3000
+4      400     4000
+
+-- !autocommit --
+1      111     1111
+2      200     2000
+3      300     3000
+4      400     4000
+
+-- !insert_select --
+1      111     1111
+2      222     2222
+3      300     3000
+4      400     4000
diff --git 
a/regression-test/framework/src/main/groovy/org/apache/doris/regression/Config.groovy
 
b/regression-test/framework/src/main/groovy/org/apache/doris/regression/Config.groovy
index a0cff933fa0..24a892bd1e5 100644
--- 
a/regression-test/framework/src/main/groovy/org/apache/doris/regression/Config.groovy
+++ 
b/regression-test/framework/src/main/groovy/org/apache/doris/regression/Config.groovy
@@ -1207,6 +1207,7 @@ class Config {
                 // schema change
                 , "test_alter_muti_modify_column"
                 // partial update
+                , "test_txn_values_partial_update"
                 , "txn_insert", "test_update_schema_change", 
"test_generated_column_update", "test_nested_type_with_rowstore", 
"test_partial_update_generated_column", 
"nereids_partial_update_native_insert_stmt"
                 , "partial_update", "nereids_update_on_current_timestamp", 
"update_on_current_timestamp", "nereids_delete_mow_partial_update", 
"delete_mow_partial_update", "test_unique_table_auto_inc"
                 , "test_unique_table_auto_inc_partial_update_correct_insert", 
"partial_update_seq_col", "nereids_partial_update_native_insert_stmt_complex", 
"regression_test_variant_delete_and_update"
diff --git 
a/regression-test/suites/insert_p0/transaction/test_txn_values_partial_update.groovy
 
b/regression-test/suites/insert_p0/transaction/test_txn_values_partial_update.groovy
new file mode 100644
index 00000000000..535c11820cd
--- /dev/null
+++ 
b/regression-test/suites/insert_p0/transaction/test_txn_values_partial_update.groovy
@@ -0,0 +1,87 @@
+// 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_txn_values_partial_update") {
+    sql "DROP TABLE IF EXISTS txn_values_partial_update"
+    sql """
+        CREATE TABLE txn_values_partial_update (
+            k INT NOT NULL,
+            v1 INT NULL,
+            v2 INT NULL
+        ) UNIQUE KEY(k)
+        DISTRIBUTED BY HASH(k) BUCKETS 1
+        PROPERTIES (
+            "replication_num" = "1",
+            "enable_unique_key_merge_on_write" = "true"
+        )
+    """
+    sql "INSERT INTO txn_values_partial_update VALUES (1, 100, 1000), (2, 200, 
2000)"
+    sql "SET enable_unique_key_partial_update = true"
+    try {
+        // Both partial updates fail before initializing the VALUES load.
+        sql "BEGIN"
+        test {
+            sql "INSERT INTO txn_values_partial_update(k, v1) VALUES (1, 111), 
(2, 222)"
+            exception "Partial update is not supported for INSERT INTO VALUES 
in an explicit transaction"
+        }
+        test {
+            sql "INSERT INTO txn_values_partial_update(k, v2) VALUES (1, 
1111), (2, 2222)"
+            exception "Partial update is not supported for INSERT INTO VALUES 
in an explicit transaction"
+        }
+        sql "COMMIT"
+        order_qt_rejected_first "SELECT * FROM txn_values_partial_update"
+
+        // A rejected first statement must not prevent a subsequent full-row 
insert.
+        sql "BEGIN"
+        test {
+            sql "INSERT INTO txn_values_partial_update(k, v1) VALUES (1, 111)"
+            exception "Partial update is not supported for INSERT INTO VALUES 
in an explicit transaction"
+        }
+        sql "INSERT INTO txn_values_partial_update VALUES (3, 300, 3000)"
+        // Reject a partial update after the stream has already been 
initialized.
+        test {
+            sql "INSERT INTO txn_values_partial_update(k, v2) VALUES (3, 3333)"
+            exception "Partial update is not supported for INSERT INTO VALUES 
in an explicit transaction"
+        }
+        sql "INSERT INTO txn_values_partial_update(k, v1, v2) VALUES (4, 400, 
4000)"
+        sql "COMMIT"
+        order_qt_commit_full_rows "SELECT * FROM txn_values_partial_update"
+
+        sql "BEGIN"
+        sql "INSERT INTO txn_values_partial_update VALUES (5, 500, 5000)"
+        test {
+            sql "INSERT INTO txn_values_partial_update(k, v2) VALUES (1, 1111)"
+            exception "Partial update is not supported for INSERT INTO VALUES 
in an explicit transaction"
+        }
+        sql "ROLLBACK"
+        order_qt_rollback "SELECT * FROM txn_values_partial_update"
+
+        // Autocommit VALUES and transactional SELECT retain partial-update 
support.
+        sql "INSERT INTO txn_values_partial_update(k, v1) VALUES (1, 111)"
+        sql "INSERT INTO txn_values_partial_update(k, v2) VALUES (1, 1111)"
+        order_qt_autocommit "SELECT * FROM txn_values_partial_update"
+
+        sql "BEGIN"
+        sql "INSERT INTO txn_values_partial_update(k, v1) SELECT 2, 222"
+        sql "INSERT INTO txn_values_partial_update(k, v2) SELECT 2, 2222"
+        sql "COMMIT"
+        order_qt_insert_select "SELECT * FROM txn_values_partial_update"
+    } finally {
+        sql "ROLLBACK"
+        sql "SET enable_unique_key_partial_update = false"
+    }
+}


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

Reply via email to