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]