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 5684b974c2b [fix](fe) Reject static partition assignments for OLAP 
tables (#70287)
5684b974c2b is described below

commit 5684b974c2bcefbd000a2671fc7b2856f9579240
Author: yujun <[email protected]>
AuthorDate: Mon Nov 9 15:25:21 2026 +0800

    [fix](fe) Reject static partition assignments for OLAP tables (#70287)
    
    Doris accepts both partition-name lists and static partition assignments
    in INSERT statements. Static assignments such as
    `PARTITION(dt='2026-11-04')` are supported by external-table write
    paths; OLAP tables use partition names, or `PARTITION(*)` for automatic
    overwrite partition detection.
    
    ### What problem does this PR solve?
    
    Issue Number: None
    Related PR: #58396 (introduced static partition overwrite syntax)
    PR Type: Bugfix
    
    Problem Summary:
    
    An INSERT OVERWRITE on an OLAP table accepts
    `PARTITION(dt='2026-11-04')` but silently discards the static partition
    assignments when creating the table sink. The resulting empty
    partition-name list is treated as a whole-table overwrite, so data in
    unrelated partitions is removed. Reject static partition assignments
    before constructing an OLAP sink, for both INSERT INTO and INSERT
    OVERWRITE, including remote Doris targets. The error directs users to
    partition names or automatic overwrite partition detection.
    
    ### Details
    
    - **Frequency:** Reproducible whenever an OLAP INSERT OVERWRITE
    specifies static partition assignments; ordinary INSERT INTO silently
    ignores the same assignments.
    - **Root cause:** The parser preserves assignments separately from
    partition names, but both OLAP sink creation entry points ignore the
    assignments.
    - **Why existing tests missed it:** Parser tests verify that assignments
    are accepted, and external-table tests exercise supported static
    partition writes. They do not check that OLAP targets reject the
    unsupported syntax or preserve unrelated partitions.
    - **Fix:** Share an admission check between both OLAP sink creation
    entry points and raise an analysis error before any write or partition
    replacement.
    - **Other paths and branches:** External connector sinks retain their
    static partition assignments. Partition-name inserts, partition-name
    overwrites, automatic partition detection, and explicit whole-table
    overwrites retain their behavior.
---
 .../nereids/analyzer/UnboundTableSinkCreator.java  |  9 +++
 .../analyzer/UnboundTableSinkCreatorTest.java      | 65 +++++++++++++++++++
 .../test_olap_static_partition_rejected.out        | 21 ++++++
 .../test_olap_static_partition_rejected.groovy     | 75 ++++++++++++++++++++++
 4 files changed, 170 insertions(+)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundTableSinkCreator.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundTableSinkCreator.java
index a83a46044d1..9cdcac6cad7 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundTableSinkCreator.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundTableSinkCreator.java
@@ -105,6 +105,7 @@ public class UnboundTableSinkCreator {
         String catalogName = 
RelationUtil.getQualifierName(ConnectContext.get(), nameParts).get(0);
         CatalogIf<?> curCatalog = 
Env.getCurrentEnv().getCatalogMgr().getCatalog(catalogName);
         if (curCatalog instanceof InternalCatalog || curCatalog instanceof 
RemoteDorisExternalCatalog) {
+            rejectStaticPartitionForOlapTable(staticPartitionKeyValues);
             return new UnboundTableSink<>(nameParts, colNames, hints, 
temporaryPartition, partitions,
                     isPartialUpdate, partialUpdateNewKeyPolicy, 
dmlCommandType, Optional.empty(),
                     Optional.empty(), plan);
@@ -136,6 +137,7 @@ public class UnboundTableSinkCreator {
         String catalogName = 
RelationUtil.getQualifierName(ConnectContext.get(), nameParts).get(0);
         CatalogIf<?> curCatalog = 
Env.getCurrentEnv().getCatalogMgr().getCatalog(catalogName);
         if (curCatalog instanceof InternalCatalog || curCatalog instanceof 
RemoteDorisExternalCatalog) {
+            rejectStaticPartitionForOlapTable(staticPartitionKeyValues);
             return new UnboundTableSink<>(nameParts, colNames, hints, 
temporaryPartition, partitions,
                     isAutoDetectPartition,
                     isPartialUpdate, partialUpdateNewKeyPolicy, 
dmlCommandType, Optional.empty(),
@@ -153,6 +155,13 @@ public class UnboundTableSinkCreator {
                         ? " PARTITION(*) is only supported in overwrite 
partition for OLAP table" : ""));
     }
 
+    private static void rejectStaticPartitionForOlapTable(Map<String, 
Expression> staticPartitionKeyValues) {
+        if (staticPartitionKeyValues != null && 
!staticPartitionKeyValues.isEmpty()) {
+            throw new AnalysisException("Static partition syntax PARTITION 
(column=value) is not supported for OLAP "
+                    + "tables. Specify partition names instead, or use 
PARTITION (*) for INSERT OVERWRITE.");
+        }
+    }
+
     private static void rejectTemporaryPartitionForExternalTable(boolean 
temporaryPartition) {
         if (temporaryPartition) {
             throw new AnalysisException("TEMPORARY PARTITION is only supported 
for internal OLAP tables");
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/analyzer/UnboundTableSinkCreatorTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/analyzer/UnboundTableSinkCreatorTest.java
index c489dbefb0d..e37d3b27145 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/analyzer/UnboundTableSinkCreatorTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/analyzer/UnboundTableSinkCreatorTest.java
@@ -22,10 +22,14 @@ import org.apache.doris.common.UserException;
 import org.apache.doris.connector.spi.Connector;
 import org.apache.doris.connector.spi.handle.WriteOperation;
 import org.apache.doris.connector.spi.write.ConnectorWritePlanProvider;
+import org.apache.doris.datasource.CatalogIf;
 import org.apache.doris.datasource.CatalogMgr;
+import org.apache.doris.datasource.InternalCatalog;
+import org.apache.doris.datasource.doris.RemoteDorisExternalCatalog;
 import org.apache.doris.datasource.plugin.PluginDrivenExternalCatalog;
 import org.apache.doris.nereids.exceptions.AnalysisException;
 import org.apache.doris.nereids.trees.expressions.Alias;
+import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.RelationId;
@@ -34,6 +38,7 @@ import 
org.apache.doris.nereids.trees.plans.logical.LogicalOneRowRelation;
 import org.apache.doris.nereids.trees.plans.logical.LogicalSink;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.mockito.MockedStatic;
@@ -41,6 +46,7 @@ import org.mockito.Mockito;
 
 import java.util.EnumSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -59,6 +65,65 @@ public class UnboundTableSinkCreatorTest {
 
     private static final List<String> NAME_PARTS = ImmutableList.of("cat", 
"db", "tbl");
 
+    @Test
+    public void staticPartitionIsRejectedForOlapTables() {
+        for (CatalogIf<?> catalog : 
ImmutableList.of(Mockito.mock(InternalCatalog.class),
+                Mockito.mock(RemoteDorisExternalCatalog.class))) {
+            withCatalog(catalog, () -> {
+                LogicalOneRowRelation query = new LogicalOneRowRelation(new 
RelationId(1),
+                        ImmutableList.of(new Alias(Literal.of(1))));
+                Map<String, Expression> staticPartitions = 
ImmutableMap.of("dt", Literal.of("2026-11-04"));
+                AnalysisException exception = 
Assertions.assertThrows(AnalysisException.class,
+                        () -> UnboundTableSinkCreator.createUnboundTableSink(
+                                NAME_PARTS, ImmutableList.of(), 
ImmutableList.of(), false,
+                                ImmutableList.of(), false, null, 
DMLCommandType.INSERT, query, staticPartitions));
+                Assertions.assertTrue(exception.getMessage().contains(
+                        "Static partition syntax PARTITION (column=value) is 
not supported for OLAP tables"));
+                for (boolean overwrite : new boolean[] {false, true}) {
+                    AnalysisException overwriteException = 
Assertions.assertThrows(AnalysisException.class,
+                            () -> 
UnboundTableSinkCreator.createUnboundTableSinkMaybeOverwrite(
+                                    NAME_PARTS, ImmutableList.of(), 
ImmutableList.of(), false,
+                                    ImmutableList.of(), false, overwrite, 
false, null,
+                                    DMLCommandType.INSERT, query, 
staticPartitions));
+                    Assertions.assertEquals(exception.getMessage(), 
overwriteException.getMessage());
+                }
+            });
+        }
+    }
+
+    @Test
+    public void staticPartitionIsPreservedForPluginDrivenExternalTables() {
+        withCatalog(Mockito.mock(PluginDrivenExternalCatalog.class), () -> {
+            LogicalOneRowRelation query = new LogicalOneRowRelation(new 
RelationId(1),
+                    ImmutableList.of(new Alias(Literal.of(1))));
+            Map<String, Expression> staticPartitions = ImmutableMap.of("dt", 
Literal.of("2026-11-04"));
+            UnboundConnectorTableSink<?> sink = (UnboundConnectorTableSink<?>)
+                    UnboundTableSinkCreator.createUnboundTableSink(
+                            NAME_PARTS, ImmutableList.of(), 
ImmutableList.of(), false,
+                            ImmutableList.of(), false, null, 
DMLCommandType.INSERT, query, staticPartitions);
+            Assertions.assertEquals(staticPartitions, 
sink.getStaticPartitionKeyValues());
+            for (boolean overwrite : new boolean[] {false, true}) {
+                UnboundConnectorTableSink<?> overwriteSink = 
(UnboundConnectorTableSink<?>)
+                        
UnboundTableSinkCreator.createUnboundTableSinkMaybeOverwrite(
+                                NAME_PARTS, ImmutableList.of(), 
ImmutableList.of(), false,
+                                ImmutableList.of(), false, overwrite, false, 
null,
+                                DMLCommandType.INSERT, query, 
staticPartitions);
+                Assertions.assertEquals(staticPartitions, 
overwriteSink.getStaticPartitionKeyValues());
+            }
+        });
+    }
+
+    private void withCatalog(CatalogIf<?> catalog, Runnable action) {
+        CatalogMgr catalogMgr = Mockito.mock(CatalogMgr.class);
+        Mockito.<Object>when(catalogMgr.getCatalog("cat")).thenReturn(catalog);
+        Env env = Mockito.mock(Env.class);
+        Mockito.when(env.getCatalogMgr()).thenReturn(catalogMgr);
+        try (MockedStatic<Env> envMock = Mockito.mockStatic(Env.class)) {
+            envMock.when(Env::getCurrentEnv).thenReturn(env);
+            action.run();
+        }
+    }
+
     @Test
     public void ctasIsRefusedWhenTheConnectorDeclaresNoWritePathAtAll() {
         // paimon / es / hudi / trino: no write plan provider at all.
diff --git 
a/regression-test/data/insert_overwrite_p0/test_olap_static_partition_rejected.out
 
b/regression-test/data/insert_overwrite_p0/test_olap_static_partition_rejected.out
new file mode 100644
index 00000000000..0cf60dd5d4c
--- /dev/null
+++ 
b/regression-test/data/insert_overwrite_p0/test_olap_static_partition_rejected.out
@@ -0,0 +1,21 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !after_rejected_statements --
+4      2026-11-04
+5      2026-11-05
+
+-- !insert_by_name --
+4      2026-11-04
+41     2026-11-04
+5      2026-11-05
+
+-- !overwrite_by_name --
+42     2026-11-04
+5      2026-11-05
+
+-- !overwrite_auto_detect --
+43     2026-11-04
+5      2026-11-05
+
+-- !overwrite_whole_table --
+44     2026-11-04
+
diff --git 
a/regression-test/suites/insert_overwrite_p0/test_olap_static_partition_rejected.groovy
 
b/regression-test/suites/insert_overwrite_p0/test_olap_static_partition_rejected.groovy
new file mode 100644
index 00000000000..ca2a04fcded
--- /dev/null
+++ 
b/regression-test/suites/insert_overwrite_p0/test_olap_static_partition_rejected.groovy
@@ -0,0 +1,75 @@
+// 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_olap_static_partition_rejected") {
+    sql "DROP TABLE IF EXISTS test_olap_static_partition_rejected"
+    sql """
+        CREATE TABLE test_olap_static_partition_rejected (
+            id INT,
+            dt DATE NOT NULL
+        ) DUPLICATE KEY(id)
+        PARTITION BY RANGE(dt) (
+            PARTITION p20261104 VALUES [('2026-11-04'), ('2026-11-05')),
+            PARTITION p20261105 VALUES [('2026-11-05'), ('2026-11-06'))
+        )
+        DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES ("replication_num" = "1")
+    """
+    sql "INSERT INTO test_olap_static_partition_rejected VALUES (4, 
'2026-11-04'), (5, '2026-11-05')"
+
+    test {
+        sql "SELECT * FROM test_olap_static_partition_rejected 
PARTITION(`dt`='2026-11-04')"
+        exception "mismatched input '='"
+    }
+    test {
+        sql """INSERT INTO test_olap_static_partition_rejected 
PARTITION(`dt`='2026-11-04')
+               VALUES (40, '2026-11-04')"""
+        exception "Static partition syntax PARTITION (column=value) is not 
supported for OLAP tables"
+    }
+    test {
+        sql """INSERT INTO test_olap_static_partition_rejected 
PARTITION(`dt`='2026-11-04')
+               SELECT 40, CAST('2026-11-05' AS DATE)"""
+        exception "Static partition syntax PARTITION (column=value) is not 
supported for OLAP tables"
+    }
+    test {
+        sql """INSERT OVERWRITE TABLE test_olap_static_partition_rejected 
PARTITION(`dt`='2026-11-04')
+               SELECT 40, CAST('2026-11-04' AS DATE)"""
+        exception "Static partition syntax PARTITION (column=value) is not 
supported for OLAP tables"
+    }
+    test {
+        sql """INSERT OVERWRITE TABLE test_olap_static_partition_rejected 
PARTITION(`dt`='2026-11-04')
+               SELECT id, dt FROM test_olap_static_partition_rejected WHERE 1 
= 0"""
+        exception "Static partition syntax PARTITION (column=value) is not 
supported for OLAP tables"
+    }
+    order_qt_after_rejected_statements "SELECT * FROM 
test_olap_static_partition_rejected"
+
+    sql """INSERT INTO test_olap_static_partition_rejected PARTITION(p20261104)
+           VALUES (41, '2026-11-04')"""
+    order_qt_insert_by_name "SELECT * FROM test_olap_static_partition_rejected"
+
+    sql """INSERT OVERWRITE TABLE test_olap_static_partition_rejected 
PARTITION(p20261104)
+           SELECT 42, CAST('2026-11-04' AS DATE)"""
+    order_qt_overwrite_by_name "SELECT * FROM 
test_olap_static_partition_rejected"
+
+    sql """INSERT OVERWRITE TABLE test_olap_static_partition_rejected 
PARTITION(*)
+           SELECT 43, CAST('2026-11-04' AS DATE)"""
+    order_qt_overwrite_auto_detect "SELECT * FROM 
test_olap_static_partition_rejected"
+
+    sql """INSERT OVERWRITE TABLE test_olap_static_partition_rejected
+           SELECT 44, CAST('2026-11-04' AS DATE)"""
+    order_qt_overwrite_whole_table "SELECT * FROM 
test_olap_static_partition_rejected"
+}


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

Reply via email to