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

morrySnow 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 25ab3efddfd [feature](partition) Forbid MAXVALUE in LIST partition 
values at DDL time (#67028)
25ab3efddfd is described below

commit 25ab3efddfd4224d871a99a149e94e3385431d86
Author: minghong <[email protected]>
AuthorDate: Tue Aug 25 19:09:01 2026 +0800

    [feature](partition) Forbid MAXVALUE in LIST partition values at DDL time 
(#67028)
    
    ### What problem does this PR solve?
    
    Related PR: #66518
    
    Problem Summary: PR #66518 made loading and pruning tolerate LIST
    partitions that contain MAXVALUE (created by older versions, e.g.
    `PARTITION p4 VALUES IN ((NULL, MAXVALUE))`), because MAXVALUE has no
    concrete value and breaks thrift serialization and predicate evaluation.
    Creating such a partition is still allowed by DDL, which keeps producing
    tables that cannot be loaded nor pruned. This change forbids using
    MAXVALUE when creating LIST partitions: CREATE TABLE and ALTER TABLE ADD
    PARTITION now fail at analysis time with an informative error that names
    the partition, the offending values, and the RANGE-only usage of
    MAXVALUE; the legacy SinglePartitionDesc path (reached e.g. by the
    INSERT OVERWRITE temp-partition swap on legacy tables) is guarded the
    same way so no new MAXVALUE LIST partition can be created through any
    path. RANGE partitions keep supporting MAXVALUE in 'VALUES LESS THAN
    (MAXVALUE)'. NULL remains a valid LIST partition value; MINVALUE is not
    a SQL keyword, so MAXVALUE is the only special partition value that
    needed to be rejected for LIST partitions. A debug point
    (FE.skipCheckMaxValueInListPartition) lets tests simulate legacy
    metadata that contains MAXVALUE LIST partitions.
    
    ### Release note
    
    Creating a LIST partition with MAXVALUE now fails with "MAXVALUE is not
    allowed in LIST partition ..." instead of succeeding and producing an
    unusable table; RANGE partitions are unaffected.
---
 .../apache/doris/analysis/PartitionKeyDesc.java    |  18 +++
 .../trees/plans/commands/info/InPartition.java     |  31 +++++
 .../doris/catalog/ListPartitionInfoTest.java       | 129 ++++++++++++-------
 .../rules/rewrite/PruneOlapScanPartitionTest.java  |  30 +++++
 .../trees/plans/CreateTableCommandTest.java        |  31 +++++
 .../plans/commands/info/CreateTableInfoTest.java   |  23 ++++
 .../partition_p0/test_list_partition_maxvalue.out  |  10 ++
 .../test_auto_list_partition_null.groovy           |  51 ++++++--
 .../test_list_partition_maxvalue.groovy            | 143 +++++++++++++++++++++
 9 files changed, 408 insertions(+), 58 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/analysis/PartitionKeyDesc.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/PartitionKeyDesc.java
index 817878fe78b..47df7d85617 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/PartitionKeyDesc.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/PartitionKeyDesc.java
@@ -18,6 +18,7 @@
 package org.apache.doris.analysis;
 
 import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.util.DebugPointUtil;
 
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
@@ -170,6 +171,23 @@ public class PartitionKeyDesc {
                 }
             }
         }
+
+        // MAXVALUE is only meaningful as the open upper bound of a RANGE 
partition
+        // ('VALUES LESS THAN (MAXVALUE)'). A LIST partition enumerates 
concrete values, so a
+        // MAXVALUE key can never be matched on load and breaks partition 
serialization and
+        // pruning afterwards. Reject it at DDL time; tables created by older 
versions keep
+        // working (see the load/prune handling that skips MAXVALUE keys).
+        if (inValues != null && 
!DebugPointUtil.isEnable("FE.skipCheckMaxValueInListPartition")) {
+            for (List<PartitionValue> inValue : inValues) {
+                for (PartitionValue value : inValue) {
+                    if (value.isMax()) {
+                        throw new AnalysisException("MAXVALUE is not allowed 
in LIST partition values: "
+                                + toSql() + ". MAXVALUE can only be used in 
RANGE partition with "
+                                + "'VALUES LESS THAN (MAXVALUE)'. Please use 
explicit values or NULL instead.");
+                    }
+                }
+            }
+        }
     }
 
     // returns:
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/InPartition.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/InPartition.java
index d77e5dbe3b7..d95d14c0583 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/InPartition.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/InPartition.java
@@ -22,6 +22,7 @@ import org.apache.doris.analysis.PartitionKeyDesc;
 import org.apache.doris.analysis.PartitionValue;
 import org.apache.doris.analysis.SinglePartitionDesc;
 import org.apache.doris.common.FeNameFormat;
+import org.apache.doris.common.util.DebugPointUtil;
 import org.apache.doris.nereids.exceptions.AnalysisException;
 import org.apache.doris.nereids.trees.expressions.Expression;
 
@@ -49,6 +50,36 @@ public class InPartition extends PartitionDefinition {
         } catch (Exception e) {
             throw new AnalysisException(e.getMessage(), e.getCause());
         }
+        checkNoMaxValue();
+    }
+
+    /**
+     * MAXVALUE is only meaningful as the open upper bound of a RANGE partition
+     * ('VALUES LESS THAN (MAXVALUE)'). A LIST partition enumerates concrete 
values, so a
+     * MAXVALUE key can never be matched on load and breaks partition 
serialization and
+     * pruning afterwards. Reject it at DDL time; tables created by older 
versions keep
+     * working (see the load/prune handling that skips MAXVALUE keys).
+     */
+    private void checkNoMaxValue() {
+        if (DebugPointUtil.isEnable("FE.skipCheckMaxValueInListPartition")) {
+            return;
+        }
+        for (List<Expression> item : values) {
+            for (Expression value : item) {
+                if (value instanceof PartitionDefinition.MaxValue) {
+                    throw new AnalysisException(String.format(
+                            "MAXVALUE is not allowed in LIST partition '%s', 
got VALUES IN (%s). "
+                                    + "MAXVALUE can only be used in RANGE 
partition with "
+                                    + "'VALUES LESS THAN (MAXVALUE)'. Please 
use explicit values or NULL instead.",
+                            partitionName,
+                            
item.stream().map(InPartition::valueToSql).collect(Collectors.joining(", "))));
+                }
+            }
+        }
+    }
+
+    private static String valueToSql(Expression value) {
+        return value instanceof PartitionDefinition.MaxValue ? "MAXVALUE" : 
value.toSql();
     }
 
     @Override
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/catalog/ListPartitionInfoTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/ListPartitionInfoTest.java
index d0e725a1b24..55883fc699e 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/catalog/ListPartitionInfoTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/ListPartitionInfoTest.java
@@ -24,7 +24,9 @@ import org.apache.doris.analysis.SinglePartitionDesc;
 import org.apache.doris.analysis.SlotRef;
 import org.apache.doris.catalog.info.TableNameInfo;
 import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.Config;
 import org.apache.doris.common.DdlException;
+import org.apache.doris.common.util.DebugPointUtil;
 
 import com.google.common.collect.Lists;
 import org.junit.Assert;
@@ -251,58 +253,91 @@ public class ListPartitionInfoTest {
 
     @Test
     public void testListPartitionNullMax() throws AnalysisException, 
DdlException {
-        PartitionItem partitionItem = null;
+        // MAXVALUE is rejected at DDL time now; this test exercises the 
catalog-level
+        // handling of legacy metadata (tables created by older versions that 
allowed
+        // MAXVALUE in LIST partitions), so bypass the DDL check with a debug 
point.
+        boolean originalEnableDebugPoints = Config.enable_debug_points;
+        Config.enable_debug_points = true;
+        try {
+            
DebugPointUtil.addDebugPoint("FE.skipCheckMaxValueInListPartition");
+            PartitionItem partitionItem = null;
+            Column k1 = new Column("k1", new ScalarType(PrimitiveType.INT), 
true, null, "", "");
+            Column k2 = new Column("k2", new ScalarType(PrimitiveType.INT), 
true, null, "", "");
+            partitionColumns.add(k1);
+            partitionColumns.add(k2);
+            partitionInfo = new ListPartitionInfo(partitionColumns);
+
+            List<List<PartitionValue>> inValues = new ArrayList<>();
+            inValues.add(Lists.newArrayList(new PartitionValue("", true), 
PartitionValue.MAX_VALUE));
+            SinglePartitionDesc singlePartitionDesc = new 
SinglePartitionDesc(false, "p1",
+                    PartitionKeyDesc.createIn(inValues), null);
+            singlePartitionDesc.analyze(2, null);
+            partitionItem = 
partitionInfo.handleNewSinglePartitionDesc(singlePartitionDesc, 20000L, false);
+
+            Assert.assertEquals("((NULL, MAXVALUE))", ((ListPartitionItem) 
partitionItem).toSql());
+
+            inValues = new ArrayList<>();
+            inValues.add(Lists.newArrayList(new PartitionValue("", true), new 
PartitionValue("", true)));
+            singlePartitionDesc = new SinglePartitionDesc(false, "p2",
+            PartitionKeyDesc.createIn(inValues), null);
+            singlePartitionDesc.analyze(2, null);
+            partitionItem = 
partitionInfo.handleNewSinglePartitionDesc(singlePartitionDesc, 20000L, false);
+
+            Assert.assertEquals("((NULL, NULL))", ((ListPartitionItem) 
partitionItem).toSql());
+
+            inValues = new ArrayList<>();
+            inValues.add(Lists.newArrayList(PartitionValue.MAX_VALUE, new 
PartitionValue("", true)));
+            singlePartitionDesc = new SinglePartitionDesc(false, "p3",
+            PartitionKeyDesc.createIn(inValues), null);
+            singlePartitionDesc.analyze(2, null);
+            partitionItem = 
partitionInfo.handleNewSinglePartitionDesc(singlePartitionDesc, 20000L, false);
+
+            Assert.assertEquals("((MAXVALUE, NULL))", ((ListPartitionItem) 
partitionItem).toSql());
+
+            inValues = new ArrayList<>();
+            inValues.add(Lists.newArrayList(PartitionValue.MAX_VALUE, 
PartitionValue.MAX_VALUE));
+            singlePartitionDesc = new SinglePartitionDesc(false, "p4",
+            PartitionKeyDesc.createIn(inValues), null);
+            singlePartitionDesc.analyze(2, null);
+            partitionItem = 
partitionInfo.handleNewSinglePartitionDesc(singlePartitionDesc, 20000L, false);
+
+            Assert.assertEquals("((MAXVALUE, MAXVALUE))", ((ListPartitionItem) 
partitionItem).toSql());
+
+            inValues = new ArrayList<>();
+            inValues.add(Lists.newArrayList(new PartitionValue("", true), new 
PartitionValue("", true)));
+            inValues.add(Lists.newArrayList(PartitionValue.MAX_VALUE, new 
PartitionValue("", true)));
+            inValues.add(Lists.newArrayList(new PartitionValue("", true), 
PartitionValue.MAX_VALUE));
+            singlePartitionDesc = new SinglePartitionDesc(false, "p5",
+            PartitionKeyDesc.createIn(inValues), null);
+            singlePartitionDesc.analyze(2, null);
+            partitionItem = 
partitionInfo.handleNewSinglePartitionDesc(singlePartitionDesc, 20000L, false);
+
+            Assert.assertEquals("((NULL, NULL),(MAXVALUE, NULL),(NULL, 
MAXVALUE))", ((ListPartitionItem) partitionItem).toSql());
+        } finally {
+            
DebugPointUtil.removeDebugPoint("FE.skipCheckMaxValueInListPartition");
+            Config.enable_debug_points = originalEnableDebugPoints;
+        }
+    }
+
+    @Test
+    public void testRejectMaxValueInListPartition() throws AnalysisException {
         Column k1 = new Column("k1", new ScalarType(PrimitiveType.INT), true, 
null, "", "");
-        Column k2 = new Column("k2", new ScalarType(PrimitiveType.INT), true, 
null, "", "");
         partitionColumns.add(k1);
-        partitionColumns.add(k2);
-        partitionInfo = new ListPartitionInfo(partitionColumns);
 
         List<List<PartitionValue>> inValues = new ArrayList<>();
-        inValues.add(Lists.newArrayList(new PartitionValue("", true), 
PartitionValue.MAX_VALUE));
+        inValues.add(Lists.newArrayList(new PartitionValue("1"), 
PartitionValue.MAX_VALUE));
         SinglePartitionDesc singlePartitionDesc = new 
SinglePartitionDesc(false, "p1",
                 PartitionKeyDesc.createIn(inValues), null);
-        singlePartitionDesc.analyze(2, null);
-        partitionItem = 
partitionInfo.handleNewSinglePartitionDesc(singlePartitionDesc, 20000L, false);
-
-        Assert.assertEquals("((NULL, MAXVALUE))", ((ListPartitionItem) 
partitionItem).toSql());
-
-        inValues = new ArrayList<>();
-        inValues.add(Lists.newArrayList(new PartitionValue("", true), new 
PartitionValue("", true)));
-        singlePartitionDesc = new SinglePartitionDesc(false, "p2",
-        PartitionKeyDesc.createIn(inValues), null);
-        singlePartitionDesc.analyze(2, null);
-        partitionItem = 
partitionInfo.handleNewSinglePartitionDesc(singlePartitionDesc, 20000L, false);
-
-        Assert.assertEquals("((NULL, NULL))", ((ListPartitionItem) 
partitionItem).toSql());
-
-        inValues = new ArrayList<>();
-        inValues.add(Lists.newArrayList(PartitionValue.MAX_VALUE, new 
PartitionValue("", true)));
-        singlePartitionDesc = new SinglePartitionDesc(false, "p3",
-        PartitionKeyDesc.createIn(inValues), null);
-        singlePartitionDesc.analyze(2, null);
-        partitionItem = 
partitionInfo.handleNewSinglePartitionDesc(singlePartitionDesc, 20000L, false);
-
-        Assert.assertEquals("((MAXVALUE, NULL))", ((ListPartitionItem) 
partitionItem).toSql());
-
-        inValues = new ArrayList<>();
-        inValues.add(Lists.newArrayList(PartitionValue.MAX_VALUE, 
PartitionValue.MAX_VALUE));
-        singlePartitionDesc = new SinglePartitionDesc(false, "p4",
-        PartitionKeyDesc.createIn(inValues), null);
-        singlePartitionDesc.analyze(2, null);
-        partitionItem = 
partitionInfo.handleNewSinglePartitionDesc(singlePartitionDesc, 20000L, false);
-
-        Assert.assertEquals("((MAXVALUE, MAXVALUE))", ((ListPartitionItem) 
partitionItem).toSql());
-
-        inValues = new ArrayList<>();
-        inValues.add(Lists.newArrayList(new PartitionValue("", true), new 
PartitionValue("", true)));
-        inValues.add(Lists.newArrayList(PartitionValue.MAX_VALUE, new 
PartitionValue("", true)));
-        inValues.add(Lists.newArrayList(new PartitionValue("", true), 
PartitionValue.MAX_VALUE));
-        singlePartitionDesc = new SinglePartitionDesc(false, "p5",
-        PartitionKeyDesc.createIn(inValues), null);
-        singlePartitionDesc.analyze(2, null);
-        partitionItem = 
partitionInfo.handleNewSinglePartitionDesc(singlePartitionDesc, 20000L, false);
-
-        Assert.assertEquals("((NULL, NULL),(MAXVALUE, NULL),(NULL, 
MAXVALUE))", ((ListPartitionItem) partitionItem).toSql());
+
+        AnalysisException ex = Assert.assertThrows(AnalysisException.class,
+                () -> singlePartitionDesc.analyze(1, null));
+        Assert.assertTrue(ex.getMessage().contains("MAXVALUE is not allowed in 
LIST partition values"));
+
+        // NULL is still a valid LIST partition value.
+        List<List<PartitionValue>> nullValues = new ArrayList<>();
+        nullValues.add(Lists.newArrayList(new PartitionValue("", true)));
+        SinglePartitionDesc nullPartitionDesc = new SinglePartitionDesc(false, 
"p2",
+                PartitionKeyDesc.createIn(nullValues), null);
+        nullPartitionDesc.analyze(1, null);
     }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartitionTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartitionTest.java
index 107c3debe1f..a59c5134f30 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartitionTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartitionTest.java
@@ -22,7 +22,9 @@ import org.apache.doris.catalog.Env;
 import org.apache.doris.catalog.MaterializedIndex;
 import org.apache.doris.catalog.OlapTable;
 import org.apache.doris.catalog.Tablet;
+import org.apache.doris.common.Config;
 import org.apache.doris.common.FeConstants;
+import org.apache.doris.common.util.DebugPointUtil;
 import org.apache.doris.nereids.util.MemoPatternMatchSupported;
 import org.apache.doris.nereids.util.PlanChecker;
 import org.apache.doris.utframe.TestWithFeService;
@@ -331,6 +333,34 @@ class PruneOlapScanPartitionTest extends TestWithFeService 
implements MemoPatter
         test("test_basic_agg", "'299.8' like '1%'", 4);
     }
 
+    @Test
+    void testListPartitionWithMaxValueNotPruned() throws Exception {
+        // Tables created by older versions may contain MAXVALUE in LIST 
partition values.
+        // Such partition keys cannot be evaluated against the predicate, so 
the partition
+        // must be kept conservatively instead of being pruned. Bypass the DDL 
check (which
+        // forbids creating new MAXVALUE LIST partitions) with a debug point.
+        boolean originalEnableDebugPoints = Config.enable_debug_points;
+        Config.enable_debug_points = true;
+        try {
+            
DebugPointUtil.addDebugPoint("FE.skipCheckMaxValueInListPartition");
+            createTable("create table test_list_maxvalue(id int, part int not 
null) "
+                    + "partition by list(part) ("
+                    + "  partition p1 values in (('1'), ('4'), ('7')),"
+                    + "  partition p2 values in ((MAXVALUE))"
+                    + ") "
+                    + "distributed by hash(id) "
+                    + "properties ('replication_num'='1')");
+        } finally {
+            
DebugPointUtil.removeDebugPoint("FE.skipCheckMaxValueInListPartition");
+            Config.enable_debug_points = originalEnableDebugPoints;
+        }
+
+        // p1 matches 'part = 1', p2 (MAXVALUE) is kept conservatively.
+        test("test_list_maxvalue", "part = 1", 2);
+        // p1 does not match, but p2 (MAXVALUE) is still kept.
+        test("test_list_maxvalue", "part = 9", 1);
+    }
+
     @Test
     void legacyTests() {
         // 1. Single partition column
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/CreateTableCommandTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/CreateTableCommandTest.java
index eda1f43a42c..f66efae7bf8 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/CreateTableCommandTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/CreateTableCommandTest.java
@@ -1029,6 +1029,37 @@ public class CreateTableCommandTest extends 
TestWithFeService {
         return command.getCreateMTMVInfo();
     }
 
+    @Test
+    public void testRejectMaxValueInListPartition() {
+        // MAXVALUE can only be used in RANGE partition's VALUES LESS THAN, it 
is not a
+        // concrete LIST partition value, so creating a LIST partition with it 
must fail.
+        String invalidSql = "create table test.tbl_list_maxvalue ("
+                + "k int not null, v int) "
+                + "duplicate key(k) "
+                + "partition by list(k) ("
+                + "  partition p1 values in (('1')),"
+                + "  partition p2 values in ((MAXVALUE))"
+                + ") "
+                + "distributed by hash(k) buckets 1 "
+                + "properties('replication_num' = '1')";
+        AnalysisException ex = Assertions.assertThrows(
+                AnalysisException.class, () -> getCreateTableStmt(invalidSql));
+        Assertions.assertTrue(ex.getMessage().contains("MAXVALUE is not 
allowed in LIST partition"));
+        Assertions.assertTrue(ex.getMessage().contains("p2"));
+
+        // RANGE partition's VALUES LESS THAN (MAXVALUE) stays allowed.
+        String validSql = "create table test.tbl_range_maxvalue ("
+                + "k int not null, v int) "
+                + "duplicate key(k) "
+                + "partition by range(k) ("
+                + "  partition p1 values less than ('10'),"
+                + "  partition p2 values less than (MAXVALUE)"
+                + ") "
+                + "distributed by hash(k) buckets 1 "
+                + "properties('replication_num' = '1')";
+        Assertions.assertDoesNotThrow(() -> getCreateTableStmt(validSql));
+    }
+
     @Test
     public void testVariantFieldPatternDictCompressionValidation() {
         String invalidSql = "create table test.tbl_variant_dict_invalid\n"
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/info/CreateTableInfoTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/info/CreateTableInfoTest.java
index 718c20673ae..a61e3296ffc 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/info/CreateTableInfoTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/info/CreateTableInfoTest.java
@@ -294,4 +294,27 @@ public class CreateTableInfoTest {
         Assertions.assertThrows(AnalysisException.class, () -> 
createTableInfo2.checkPartitionNullity(columnDefs2, partitionTableInfo2),
                 "Can't have null partition is for NOT NULL partition column in 
partition expr's index 0");
     }
+
+    /**
+     * MAXVALUE can only be used in RANGE partition's VALUES LESS THAN, it is 
not a
+     * concrete LIST partition value, so InPartition.validate() must reject it.
+     */
+    @Test
+    public void testInPartitionRejectMaxValue() {
+        List<List<Expression>> values = new ArrayList<>();
+        List<Expression> innerValues = new ArrayList<>();
+        innerValues.add(PartitionDefinition.MaxValue.INSTANCE);
+        values.add(innerValues);
+        InPartition inPartition = new InPartition(false, "p1", values);
+        AnalysisException ex = Assertions.assertThrows(AnalysisException.class,
+                () -> inPartition.validate(new HashMap<>()));
+        Assertions.assertTrue(ex.getMessage().contains("MAXVALUE is not 
allowed in LIST partition 'p1'"));
+        Assertions.assertTrue(ex.getMessage().contains("VALUES IN 
(MAXVALUE)"));
+
+        // NULL is still a valid LIST partition value.
+        List<List<Expression>> nullValues = new ArrayList<>();
+        nullValues.add(Lists.newArrayList((Expression) NullLiteral.INSTANCE));
+        InPartition nullPartition = new InPartition(false, "p2", nullValues);
+        Assertions.assertDoesNotThrow(() -> nullPartition.validate(new 
HashMap<>()));
+    }
 }
diff --git a/regression-test/data/partition_p0/test_list_partition_maxvalue.out 
b/regression-test/data/partition_p0/test_list_partition_maxvalue.out
new file mode 100644
index 00000000000..2c5d1fc1549
--- /dev/null
+++ b/regression-test/data/partition_p0/test_list_partition_maxvalue.out
@@ -0,0 +1,10 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !select_all --
+\N     \N
+\N     1
+1      \N
+2      2
+
+-- !select_with_predicate --
+2      2
+
diff --git 
a/regression-test/suites/partition_p0/auto_partition/test_auto_list_partition_null.groovy
 
b/regression-test/suites/partition_p0/auto_partition/test_auto_list_partition_null.groovy
index 7bddb5e2c18..c0eb8f35c51 100644
--- 
a/regression-test/suites/partition_p0/auto_partition/test_auto_list_partition_null.groovy
+++ 
b/regression-test/suites/partition_p0/auto_partition/test_auto_list_partition_null.groovy
@@ -17,6 +17,26 @@
 
 suite("test_auto_list_partition_null") {
 
+    // MAXVALUE is not allowed in LIST partition values when creating a table,
+    // it can only be used in RANGE partition's VALUES LESS THAN (MAXVALUE).
+    test {
+        sql """
+            CREATE TABLE list_table_maxvalue_err (
+                id int null,
+                k largeint null
+            )
+            PARTITION BY LIST (`id`, `k`)
+            (
+                PARTITION p1 VALUES IN ((NULL, MAXVALUE))
+            )
+            DISTRIBUTED BY HASH(`k`) BUCKETS 16
+            PROPERTIES (
+                "replication_allocation" = "tag.location.default: 1"
+            );
+            """
+        exception "MAXVALUE is not allowed in LIST partition"
+    }
+
     sql "DROP TABLE IF EXISTS list_table_null"
 
     sql """
@@ -35,29 +55,38 @@ suite("test_auto_list_partition_null") {
     sql """ ALTER TABLE `list_table_null` ADD PARTITION `p1` VALUES IN ((NULL, 
"1")) ("version_info" = "1") DISTRIBUTED BY HASH(`k`) BUCKETS 16; """
     sql """ ALTER TABLE `list_table_null` ADD PARTITION `p2` VALUES IN (("1", 
NULL)) ("version_info" = "1") DISTRIBUTED BY HASH(`k`) BUCKETS 16; """
     sql """ ALTER TABLE `list_table_null` ADD PARTITION `p3` VALUES IN ((NULL, 
NULL)) ("version_info" = "1") DISTRIBUTED BY HASH(`k`) BUCKETS 16; """
-    sql """ ALTER TABLE `list_table_null` ADD PARTITION `p4` VALUES IN ((NULL, 
MAXVALUE)) ("version_info" = "1") DISTRIBUTED BY HASH(`k`) BUCKETS 16; """
-    sql """ ALTER TABLE `list_table_null` ADD PARTITION `p5` VALUES IN 
((MAXVALUE, NULL)) ("version_info" = "1") DISTRIBUTED BY HASH(`k`) BUCKETS 16; 
"""
-    sql """ ALTER TABLE `list_table_null` ADD PARTITION `p6` VALUES IN (("1", 
MAXVALUE)) ("version_info" = "1") DISTRIBUTED BY HASH(`k`) BUCKETS 16; """
-    sql """ ALTER TABLE `list_table_null` ADD PARTITION `p7` VALUES IN 
((MAXVALUE, "1")) ("version_info" = "1") DISTRIBUTED BY HASH(`k`) BUCKETS 16; 
"""
+    // MAXVALUE is not allowed in LIST partition values, it can only be used 
in RANGE
+    // partition's VALUES LESS THAN (MAXVALUE).
+    test {
+        sql """ ALTER TABLE `list_table_null` ADD PARTITION `p4` VALUES IN 
((NULL, MAXVALUE)) ("version_info" = "1") DISTRIBUTED BY HASH(`k`) BUCKETS 16; 
"""
+        exception "MAXVALUE is not allowed in LIST partition"
+    }
+    test {
+        sql """ ALTER TABLE `list_table_null` ADD PARTITION `p5` VALUES IN 
((MAXVALUE, NULL)) ("version_info" = "1") DISTRIBUTED BY HASH(`k`) BUCKETS 16; 
"""
+        exception "MAXVALUE is not allowed in LIST partition"
+    }
+    test {
+        sql """ ALTER TABLE `list_table_null` ADD PARTITION `p6` VALUES IN 
(("1", MAXVALUE)) ("version_info" = "1") DISTRIBUTED BY HASH(`k`) BUCKETS 16; 
"""
+        exception "MAXVALUE is not allowed in LIST partition"
+    }
+    test {
+        sql """ ALTER TABLE `list_table_null` ADD PARTITION `p7` VALUES IN 
((MAXVALUE, "1")) ("version_info" = "1") DISTRIBUTED BY HASH(`k`) BUCKETS 16; 
"""
+        exception "MAXVALUE is not allowed in LIST partition"
+    }
 
     def res = sql "show create table list_table_null"
 
     assertTrue(res[0][1].contains("PARTITION p3 VALUES IN ((NULL, NULL))"))
     assertTrue(res[0][1].contains("PARTITION p1 VALUES IN ((NULL, \"1\"))"))
-    assertTrue(res[0][1].contains("PARTITION p4 VALUES IN ((NULL, MAXVALUE))"))
     assertTrue(res[0][1].contains("PARTITION p2 VALUES IN ((\"1\", NULL))"))
-    assertTrue(res[0][1].contains("PARTITION p6 VALUES IN ((\"1\", 
MAXVALUE))"))
-    assertTrue(res[0][1].contains("PARTITION p5 VALUES IN ((MAXVALUE, NULL))"))
-    assertTrue(res[0][1].contains("PARTITION p7 VALUES IN ((MAXVALUE, 
\"1\"))"))
 
-    // Insert into a table containing MAXVALUE list partitions should not fail.
+    // Insert into a table containing NULL list partitions should not fail.
     // (NULL, "1") -> p1, ("1", NULL) -> p2, (NULL, NULL) -> p3,
     // ("2", "2") matches no predefined partition and is auto-created since 
the table is AUTO.
     sql """ insert into list_table_null values (null, "1"), ("1", null), 
(null, null), ("2", "2") """
 
     order_qt_select_all """ select * from list_table_null order by id, k """
 
-    // Predicate on the partition columns must not crash partition pruning:
-    // partition keys containing MAXVALUE cannot be evaluated, they are kept 
conservatively.
+    // Predicate on the partition columns must not crash partition pruning.
     order_qt_select_with_predicate """ select * from list_table_null where id 
= 2 and k = 2 order by id, k """
 }
diff --git 
a/regression-test/suites/partition_p0/test_list_partition_maxvalue.groovy 
b/regression-test/suites/partition_p0/test_list_partition_maxvalue.groovy
new file mode 100644
index 00000000000..e9440597d7f
--- /dev/null
+++ b/regression-test/suites/partition_p0/test_list_partition_maxvalue.groovy
@@ -0,0 +1,143 @@
+// 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 org.apache.doris.regression.suite.ClusterOptions
+
+// MAXVALUE is forbidden in LIST partition values at DDL time. This suite 
verifies the
+// rejection and, via a debug point that bypasses the check (simulating tables 
created by
+// older versions that allowed MAXVALUE in LIST partitions), verifies that 
such legacy
+// tables still load and query correctly.
+suite("test_list_partition_maxvalue", "docker") {
+    def options = new ClusterOptions()
+    options.enableDebugPoints()
+
+    docker(options) {
+        sleep 2000
+        try {
+            // 1. MAXVALUE in CREATE TABLE list partition is rejected.
+            test {
+                sql """
+                    CREATE TABLE list_table_maxvalue_err (
+                        id int null,
+                        k largeint null
+                    )
+                    PARTITION BY LIST (`id`, `k`)
+                    (
+                        PARTITION p1 VALUES IN ((NULL, MAXVALUE))
+                    )
+                    DISTRIBUTED BY HASH(`k`) BUCKETS 16
+                    PROPERTIES (
+                        "replication_allocation" = "tag.location.default: 1"
+                    );
+                    """
+                exception "MAXVALUE is not allowed in LIST partition"
+            }
+
+            // 2. RANGE partition's VALUES LESS THAN (MAXVALUE) is still 
allowed.
+            sql """
+                CREATE TABLE range_table_maxvalue_ok (
+                    id int null,
+                    k largeint null
+                )
+                PARTITION BY RANGE (`k`)
+                (
+                    PARTITION p1 VALUES LESS THAN ("100"),
+                    PARTITION p2 VALUES LESS THAN (MAXVALUE)
+                )
+                DISTRIBUTED BY HASH(`k`) BUCKETS 16
+                PROPERTIES (
+                    "replication_allocation" = "tag.location.default: 1"
+                );
+                """
+
+            // 3. ALTER TABLE ADD PARTITION with MAXVALUE is rejected too.
+            sql """
+                CREATE TABLE list_table_alter_err (
+                    id int null,
+                    k largeint null
+                )
+                PARTITION BY LIST (`id`, `k`)
+                (
+                    PARTITION p1 VALUES IN ((NULL, "1"))
+                )
+                DISTRIBUTED BY HASH(`k`) BUCKETS 16
+                PROPERTIES (
+                    "replication_allocation" = "tag.location.default: 1"
+                );
+                """
+            test {
+                sql """ ALTER TABLE `list_table_alter_err` ADD PARTITION `p2` 
VALUES IN ((NULL, MAXVALUE)) ("version_info" = "1") DISTRIBUTED BY HASH(`k`) 
BUCKETS 16; """
+                exception "MAXVALUE is not allowed in LIST partition"
+            }
+
+            // 4. Simulate a table created by an older version (which allowed 
MAXVALUE in
+            // LIST partitions): bypass the DDL check with a debug point, then 
verify that
+            // loading and pruning such legacy tables still works.
+            
GetDebugPoint().enableDebugPointForAllFEs('FE.skipCheckMaxValueInListPartition',
 null)
+            try {
+                sql """
+                    CREATE TABLE list_table_null (
+                        id int null,
+                        k largeint null
+                    )
+                    AUTO PARTITION BY LIST (`id`, `k`)
+                    (
+                    )
+                    DISTRIBUTED BY HASH(`k`) BUCKETS 16
+                    PROPERTIES (
+                        "replication_allocation" = "tag.location.default: 1"
+                    );
+                    """
+                sql """ ALTER TABLE `list_table_null` ADD PARTITION `p1` 
VALUES IN ((NULL, "1")) ("version_info" = "1") DISTRIBUTED BY HASH(`k`) BUCKETS 
16; """
+                sql """ ALTER TABLE `list_table_null` ADD PARTITION `p2` 
VALUES IN (("1", NULL)) ("version_info" = "1") DISTRIBUTED BY HASH(`k`) BUCKETS 
16; """
+                sql """ ALTER TABLE `list_table_null` ADD PARTITION `p3` 
VALUES IN ((NULL, NULL)) ("version_info" = "1") DISTRIBUTED BY HASH(`k`) 
BUCKETS 16; """
+                sql """ ALTER TABLE `list_table_null` ADD PARTITION `p4` 
VALUES IN ((NULL, MAXVALUE)) ("version_info" = "1") DISTRIBUTED BY HASH(`k`) 
BUCKETS 16; """
+                sql """ ALTER TABLE `list_table_null` ADD PARTITION `p5` 
VALUES IN ((MAXVALUE, NULL)) ("version_info" = "1") DISTRIBUTED BY HASH(`k`) 
BUCKETS 16; """
+                sql """ ALTER TABLE `list_table_null` ADD PARTITION `p6` 
VALUES IN (("1", MAXVALUE)) ("version_info" = "1") DISTRIBUTED BY HASH(`k`) 
BUCKETS 16; """
+                sql """ ALTER TABLE `list_table_null` ADD PARTITION `p7` 
VALUES IN ((MAXVALUE, "1")) ("version_info" = "1") DISTRIBUTED BY HASH(`k`) 
BUCKETS 16; """
+            } finally {
+                
GetDebugPoint().disableDebugPointForAllFEs('FE.skipCheckMaxValueInListPartition')
+            }
+
+            def res = sql "show create table list_table_null"
+            assertTrue(res[0][1].contains("PARTITION p4 VALUES IN ((NULL, 
MAXVALUE))"))
+            assertTrue(res[0][1].contains("PARTITION p6 VALUES IN ((\"1\", 
MAXVALUE))"))
+            assertTrue(res[0][1].contains("PARTITION p5 VALUES IN ((MAXVALUE, 
NULL))"))
+            assertTrue(res[0][1].contains("PARTITION p7 VALUES IN ((MAXVALUE, 
\"1\"))"))
+
+            // Insert into a table containing MAXVALUE list partitions should 
not fail.
+            // (NULL, "1") -> p1, ("1", NULL) -> p2, (NULL, NULL) -> p3,
+            // ("2", "2") matches no predefined partition and is auto-created 
since the table is AUTO.
+            sql """ insert into list_table_null values (null, "1"), ("1", 
null), (null, null), ("2", "2") """
+
+            order_qt_select_all """ select * from list_table_null order by id, 
k """
+
+            // Predicate on the partition columns must not crash partition 
pruning:
+            // partition keys containing MAXVALUE cannot be evaluated, they 
are kept conservatively.
+            order_qt_select_with_predicate """ select * from list_table_null 
where id = 2 and k = 2 order by id, k """
+
+            // 5. INSERT OVERWRITE on a legacy MAXVALUE table is rejected as 
well: the temp
+            // partition swap clones the MAXVALUE partition key, which is 
forbidden at DDL time.
+            test {
+                sql "INSERT OVERWRITE TABLE `list_table_null` VALUES (null, 
\"1\");"
+                exception "MAXVALUE is not allowed in LIST partition values"
+            }
+        } finally {
+            GetDebugPoint().clearDebugPointsForAllFEs()
+        }
+    }
+}


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

Reply via email to