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]