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

yiguolei 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 28d590767af [fix](catalog) Handle incomplete dynamic partition 
properties (#63831)
28d590767af is described below

commit 28d590767af466b69f3956dc2e3b8b1aaccb082d
Author: Ryan19929 <[email protected]>
AuthorDate: Mon Jun 15 11:42:14 2026 +0800

    [fix](catalog) Handle incomplete dynamic partition properties (#63831)
    
    Prevent optional dynamic partition storage properties from creating
    incomplete metadata on non-dynamic tables, and tolerate historical
    incomplete dynamic partition properties during table metadata copy.
    
    ### What problem does this PR solve?
    
    Issue Number: close #xxx
    
    Related PR: #xxx
    
    Problem Summary:
    
    A normal (non-dynamic) table can be silently left with **incomplete
    dynamic partition metadata** — e.g. only
    `dynamic_partition.storage_medium` or `dynamic_partition.storage_policy`
    — which later makes BACKUP / CCR backup snapshot fail with `failed to
    copy table` (`NumberFormatException: Cannot parse null string`).
    
    #### How it happens
    
    1. `DynamicPartitionUtil.checkInputDynamicPartitionProperties()`
    validates the core keys (`time_unit` / `end` / `prefix` / `buckets` ...)
    but **not** `storage_medium` / `storage_policy` — so setting only one of
    them on a non-dynamic table is not rejected as an incomplete config.
    2. The raw property is written into `TableProperty` **before**
    `DynamicPartitionProperty` is built; building it then runs
    `Integer.parseInt(end = null)` and throws `Cannot parse null string`.
    The ALTER fails, but the dirty property already stays in memory / image
    — and `SHOW CREATE TABLE` shows nothing, so the table looks clean.
    3. A later **BACKUP / CCR snapshot / image replay** path calls
    `OlapTable.selectiveCopy()`, which rebuilds `TableProperty` via Gson and
    re-runs the same `parseInt(null)`, finally failing with `failed to copy
    table`.
    
    #### How to reproduce
    
    ```sql
    -- 1) A normal (non-dynamic) RANGE table
    CREATE TABLE t_repro (k DATE, v INT)
    DUPLICATE KEY(k)
    PARTITION BY RANGE(k) (
      PARTITION p1 VALUES [('2026-05-26'), ('2026-05-27')),
      PARTITION p2 VALUES [('2026-05-27'), ('2026-05-28'))
    )
    DISTRIBUTED BY HASH(k) BUCKETS 1
    PROPERTIES ("replication_num" = "1");
    
    -- 2) Set ONLY storage_medium -> fails, but leaves a dirty property behind
    ALTER TABLE t_repro SET ("dynamic_partition.storage_medium" = "hdd");
    -- ERROR 1105: Cannot parse null string
    -- (SHOW CREATE TABLE still shows no dynamic_partition.*)
    
    -- 3) A later backup (or CCR full sync) fails
    BACKUP SNAPSHOT db.snap TO `__keep_on_local__` ON (t_repro) PROPERTIES 
("type" = "full");
    -- => failed to copy table: t_repro
    ```
    
    Stack: `selectiveCopy` → `gsonPostProcess` →
    `DynamicPartitionProperty.<init>` → `parseInt(end=null)` → `Cannot parse
    null string`
    
    #### The fix
    
    A two-layer defense plus better observability:
    
    1. **Source interception** — make `dynamic_partition.storage_medium` /
    `storage_policy` participate in
    `checkInputDynamicPartitionProperties()`, so setting only these on a
    non-dynamic table is rejected up front ("... is not a dynamic partition
    table") instead of leaving dirty metadata.
    2. **Deserialization tolerance** — when rebuilding `TableProperty`,
    ignore non-whitelisted `dynamic_partition.*` keys, and if required keys
    are missing, downgrade the table to non-dynamic (build an empty
    `DynamicPartitionProperty`) instead of throwing. This keeps BACKUP / CCR
    / image replay working on existing dirty metadata.
    3. **Observability** — on such a downgrade,
    `OlapTable.gsonPostProcess()` logs the table id/name and the offending
    properties, so the affected table can be located from the FE log.
    
    Note: this fix does not actively delete the incomplete properties from
    the underlying map; it only ignores them during
    `DynamicPartitionProperty` construction to avoid exceptions.
    
    ### Release note
    
    None
    
    ### Check List (For Author)
    
    - Test
        - [ ] Regression test
        - [ ] Unit Test
        - [ ] Manual test (add detailed scripts or steps below)
        - [ ] No need to test or manual test. Explain why:
    - [ ] This is a refactor/code format and no logic has been changed.
            - [ ] Previous test can cover this change.
            - [ ] No code files have been changed.
            - [ ] Other reason
    
    - Behavior changed:
        - [ ] No.
        - [ ] Yes.
    
    - Does this need documentation?
        - [ ] No.
        - [ ] Yes.
    
    ### Check List (For Reviewer who merge this PR)
    
    - [ ] Confirm the release note
    - [ ] Confirm test cases
    - [ ] Confirm document
    - [ ] Add branch pick label
    
    ---------
    
    Co-authored-by: Cursor <[email protected]>
---
 .../java/org/apache/doris/catalog/OlapTable.java   |   6 ++
 .../org/apache/doris/catalog/TableProperty.java    |  28 ++++++
 .../doris/common/util/DynamicPartitionUtil.java    |   6 +-
 .../org/apache/doris/backup/BackupJobTest.java     |  29 ++++++
 .../doris/catalog/DynamicPartitionTableTest.java   |  96 ++++++++++++++++++
 .../apache/doris/catalog/TablePropertyTest.java    | 108 +++++++++++++++++++++
 6 files changed, 272 insertions(+), 1 deletion(-)

diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
index a4a880a5f56..970999ef80f 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
@@ -2110,6 +2110,12 @@ public class OlapTable extends Table implements 
MTMVRelatedTableIf, GsonPostProc
         // After that, some properties of fullSchema and nameToColumn may be 
not same as properties of base columns.
         // So, here we need to rebuild the fullSchema to ensure the 
correctness of the properties.
         rebuildFullSchema();
+
+        if (tableProperty != null && 
tableProperty.hasInvalidDynamicPartition()) {
+            LOG.warn("Table [{}-{}] has incomplete dynamic partition 
properties {}, "
+                    + "treat it as a non-dynamic-partition table.",
+                    name, id, 
tableProperty.getOriginDynamicPartitionProperty());
+        }
     }
 
     public OlapTable selectiveCopy(Collection<String> reservedPartitions, 
IndexExtState extState, boolean isForBackup) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/TableProperty.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/TableProperty.java
index de4b804ed7f..9dd61f0969f 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/TableProperty.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/TableProperty.java
@@ -61,6 +61,10 @@ public class TableProperty implements GsonPostProcessable {
     // the follower variables are built from "properties"
     private DynamicPartitionProperty dynamicPartitionProperty =
             
EnvFactory.getInstance().createDynamicPartitionProperty(Maps.newHashMap());
+    // True when "properties" carries dynamic_partition.* entries that are 
incomplete
+    // (missing required keys) and were ignored when building 
dynamicPartitionProperty.
+    // Derived from "properties", not persisted.
+    private boolean invalidDynamicPartition = false;
     private ReplicaAllocation replicaAlloc = 
ReplicaAllocation.DEFAULT_ALLOCATION;
     private boolean isInMemory = false;
     private short minLoadReplicaNum = -1;
@@ -226,15 +230,39 @@ public class TableProperty implements GsonPostProcessable 
{
             if 
(entry.getKey().startsWith(DynamicPartitionProperty.DYNAMIC_PARTITION_PROPERTY_PREFIX))
 {
                 if 
(!DynamicPartitionProperty.DYNAMIC_PARTITION_PROPERTIES.contains(entry.getKey()))
 {
                     LOG.warn("Ignore invalid dynamic property key: {}: value: 
{}", entry.getKey(), entry.getValue());
+                    continue;
                 }
                 dynamicPartitionProperties.put(entry.getKey(), 
entry.getValue());
             }
         }
 
+        if (!dynamicPartitionProperties.isEmpty()
+                && 
!hasRequiredDynamicPartitionProperties(dynamicPartitionProperties)) {
+            LOG.warn("Ignore incomplete dynamic partition properties: {}", 
dynamicPartitionProperties);
+            dynamicPartitionProperty = 
EnvFactory.getInstance().createDynamicPartitionProperty(Maps.newHashMap());
+            invalidDynamicPartition = true;
+            return this;
+        }
+        invalidDynamicPartition = false;
         dynamicPartitionProperty = 
EnvFactory.getInstance().createDynamicPartitionProperty(dynamicPartitionProperties);
         return this;
     }
 
+    // Required keys of a usable dynamic partition config. END/BUCKETS have no 
null-safe default
+    // in DynamicPartitionProperty's constructor (Integer.parseInt), so a raw 
map carrying only
+    // optional keys (e.g. a leftover storage_medium/storage_policy from a 
failed ALTER) must be
+    // rejected here to avoid parseInt(null) during backup/selectiveCopy/image 
replay.
+    private boolean hasRequiredDynamicPartitionProperties(Map<String, String> 
dynamicPartitionProperties) {
+        return 
dynamicPartitionProperties.containsKey(DynamicPartitionProperty.TIME_UNIT)
+                && 
dynamicPartitionProperties.containsKey(DynamicPartitionProperty.END)
+                && 
dynamicPartitionProperties.containsKey(DynamicPartitionProperty.PREFIX)
+                && 
dynamicPartitionProperties.containsKey(DynamicPartitionProperty.BUCKETS);
+    }
+
+    public boolean hasInvalidDynamicPartition() {
+        return invalidDynamicPartition;
+    }
+
     public TableProperty buildInMemory() {
         isInMemory = 
Boolean.parseBoolean(properties.getOrDefault(PropertyAnalyzer.PROPERTIES_INMEMORY,
 "false"));
         return this;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/common/util/DynamicPartitionUtil.java
 
b/fe/fe-core/src/main/java/org/apache/doris/common/util/DynamicPartitionUtil.java
index 09b4a9f18e8..f1c2bb28fc4 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/common/util/DynamicPartitionUtil.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/common/util/DynamicPartitionUtil.java
@@ -465,6 +465,8 @@ public class DynamicPartitionUtil {
         String createHistoryPartition = 
properties.get(DynamicPartitionProperty.CREATE_HISTORY_PARTITION);
         String historyPartitionNum = 
properties.get(DynamicPartitionProperty.HISTORY_PARTITION_NUM);
         String reservedHistoryPeriods = 
properties.get(DynamicPartitionProperty.RESERVED_HISTORY_PERIODS);
+        String storagePolicy = 
properties.get(DynamicPartitionProperty.STORAGE_POLICY);
+        String storageMedium = 
properties.get(DynamicPartitionProperty.STORAGE_MEDIUM);
 
         if (!(Strings.isNullOrEmpty(enable)
                 && Strings.isNullOrEmpty(timeUnit)
@@ -475,7 +477,9 @@ public class DynamicPartitionUtil {
                 && Strings.isNullOrEmpty(buckets)
                 && Strings.isNullOrEmpty(createHistoryPartition)
                 && Strings.isNullOrEmpty(historyPartitionNum)
-                && Strings.isNullOrEmpty(reservedHistoryPeriods))) {
+                && Strings.isNullOrEmpty(reservedHistoryPeriods)
+                && Strings.isNullOrEmpty(storagePolicy)
+                && Strings.isNullOrEmpty(storageMedium))) {
             if (Strings.isNullOrEmpty(enable)) {
                 properties.put(DynamicPartitionProperty.ENABLE, "true");
             }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/backup/BackupJobTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/backup/BackupJobTest.java
index 20d33b9cb57..58cb556716a 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/backup/BackupJobTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/backup/BackupJobTest.java
@@ -20,9 +20,12 @@ package org.apache.doris.backup;
 import org.apache.doris.analysis.StorageBackend;
 import org.apache.doris.backup.BackupJob.BackupJobState;
 import org.apache.doris.catalog.Database;
+import org.apache.doris.catalog.DynamicPartitionProperty;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.catalog.FsBroker;
+import org.apache.doris.catalog.MaterializedIndex.IndexExtState;
 import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.catalog.TableProperty;
 import org.apache.doris.catalog.info.TableNameInfo;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.Config;
@@ -352,6 +355,32 @@ public class BackupJobTest {
         Assert.assertEquals(BackupJobState.FINISHED, job.getState());
     }
 
+    @Test
+    public void testBackupCopyTableWithDirtyDynamicPartitionStorageMedium() {
+        Map<String, String> dirtyProperties = Maps.newHashMap();
+        dirtyProperties.put(DynamicPartitionProperty.STORAGE_MEDIUM, "hdd");
+        table2.setTableProperty(new TableProperty(dirtyProperties));
+
+        Assert.assertFalse(table2.dynamicPartitionExists());
+        OlapTable copied = table2.selectiveCopy(null, IndexExtState.VISIBLE, 
true);
+        Assert.assertNotNull(copied);
+        Assert.assertFalse(copied.dynamicPartitionExists());
+        
Assert.assertTrue(copied.getTableProperty().hasInvalidDynamicPartition());
+    }
+
+    @Test
+    public void testBackupCopyTableWithDirtyDynamicPartitionStoragePolicy() {
+        Map<String, String> dirtyProperties = Maps.newHashMap();
+        dirtyProperties.put(DynamicPartitionProperty.STORAGE_POLICY, 
"test_policy");
+        table2.setTableProperty(new TableProperty(dirtyProperties));
+
+        Assert.assertFalse(table2.dynamicPartitionExists());
+        OlapTable copied = table2.selectiveCopy(null, IndexExtState.VISIBLE, 
true);
+        Assert.assertNotNull(copied);
+        Assert.assertFalse(copied.dynamicPartitionExists());
+        
Assert.assertTrue(copied.getTableProperty().hasInvalidDynamicPartition());
+    }
+
     /**
      * Test backup job execution with non-existent table
      *
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/catalog/DynamicPartitionTableTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/DynamicPartitionTableTest.java
index 8b13325db69..8afaf7d325e 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/catalog/DynamicPartitionTableTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/DynamicPartitionTableTest.java
@@ -1721,6 +1721,102 @@ public class DynamicPartitionTableTest {
         Assert.assertTrue(partitions.isEmpty());
     }
 
+    @Test
+    public void testRejectDynamicPartitionStorageMediumOnNonDynamicTable() 
throws Exception {
+        String createOlapTblStmt = "CREATE TABLE 
test.`non_dynamic_storage_medium` (\n"
+                + "  `k1` date NULL COMMENT \"\",\n"
+                + "  `v1` int NULL COMMENT \"\"\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(`k1`)\n"
+                + "PARTITION BY RANGE(`k1`)\n"
+                + "(\n"
+                + "PARTITION p1 VALUES [('2026-05-26'), ('2026-05-27')),\n"
+                + "PARTITION p2 VALUES [('2026-05-27'), ('2026-05-28'))\n"
+                + ")\n"
+                + "DISTRIBUTED BY HASH(`k1`) BUCKETS 1\n"
+                + "PROPERTIES (\n"
+                + "\"replication_num\" = \"1\"\n"
+                + ");";
+        createTable(createOlapTblStmt);
+
+        String alterStmt = "ALTER TABLE test.`non_dynamic_storage_medium` "
+                + "SET (\"dynamic_partition.storage_medium\" = \"hdd\")";
+        ExceptionChecker.expectThrowsWithMsg(DdlException.class,
+                "is not a dynamic partition table",
+                () -> alterTable(alterStmt));
+
+        Database db = 
Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");
+        OlapTable table = (OlapTable) 
db.getTableOrAnalysisException("non_dynamic_storage_medium");
+        Assert.assertFalse(table.dynamicPartitionExists());
+        Assert.assertFalse(table.getTableProperty().getProperties()
+                .containsKey(DynamicPartitionProperty.STORAGE_MEDIUM));
+        Assert.assertNotNull(table.selectiveCopy(null, IndexExtState.VISIBLE, 
true));
+    }
+
+    @Test
+    public void testRejectDynamicPartitionStoragePolicyOnNonDynamicTable() 
throws Exception {
+        String createOlapTblStmt = "CREATE TABLE 
test.`non_dynamic_storage_policy` (\n"
+                + "  `k1` date NULL COMMENT \"\",\n"
+                + "  `v1` int NULL COMMENT \"\"\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(`k1`)\n"
+                + "PARTITION BY RANGE(`k1`)\n"
+                + "(\n"
+                + "PARTITION p1 VALUES [('2026-05-26'), ('2026-05-27')),\n"
+                + "PARTITION p2 VALUES [('2026-05-27'), ('2026-05-28'))\n"
+                + ")\n"
+                + "DISTRIBUTED BY HASH(`k1`) BUCKETS 1\n"
+                + "PROPERTIES (\n"
+                + "\"replication_num\" = \"1\"\n"
+                + ");";
+        createTable(createOlapTblStmt);
+
+        String alterStmt = "ALTER TABLE test.`non_dynamic_storage_policy` "
+                + "SET (\"dynamic_partition.storage_policy\" = 
\"test_policy\")";
+        ExceptionChecker.expectThrowsWithMsg(DdlException.class,
+                "is not a dynamic partition table",
+                () -> alterTable(alterStmt));
+
+        Database db = 
Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");
+        OlapTable table = (OlapTable) 
db.getTableOrAnalysisException("non_dynamic_storage_policy");
+        Assert.assertFalse(table.dynamicPartitionExists());
+        Assert.assertFalse(table.getTableProperty().getProperties()
+                .containsKey(DynamicPartitionProperty.STORAGE_POLICY));
+        Assert.assertNotNull(table.selectiveCopy(null, IndexExtState.VISIBLE, 
true));
+    }
+
+    @Test
+    public void testAlterDynamicPartitionStorageMediumOnDynamicTable() throws 
Exception {
+        String createOlapTblStmt = "CREATE TABLE test.`dynamic_storage_medium` 
(\n"
+                + "  `k1` date NULL COMMENT \"\",\n"
+                + "  `v1` int NULL COMMENT \"\"\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(`k1`)\n"
+                + "PARTITION BY RANGE(`k1`)()\n"
+                + "DISTRIBUTED BY HASH(`k1`) BUCKETS 1\n"
+                + "PROPERTIES (\n"
+                + "\"replication_num\" = \"1\",\n"
+                + "\"dynamic_partition.enable\" = \"true\",\n"
+                + "\"dynamic_partition.time_unit\" = \"DAY\",\n"
+                + "\"dynamic_partition.start\" = \"-1\",\n"
+                + "\"dynamic_partition.end\" = \"3\",\n"
+                + "\"dynamic_partition.prefix\" = \"p\",\n"
+                + "\"dynamic_partition.buckets\" = \"1\"\n"
+                + ");";
+        createTable(createOlapTblStmt);
+
+        String alterStmt = "ALTER TABLE test.`dynamic_storage_medium` "
+                + "SET (\"dynamic_partition.storage_medium\" = \"hdd\")";
+        ExceptionChecker.expectThrowsNoException(() -> alterTable(alterStmt));
+
+        Database db = 
Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");
+        OlapTable table = (OlapTable) 
db.getTableOrAnalysisException("dynamic_storage_medium");
+        Assert.assertTrue(table.dynamicPartitionExists());
+        Assert.assertEquals("hdd", 
table.getTableProperty().getDynamicPartitionProperty().getStorageMedium());
+        Assert.assertEquals(3, 
table.getTableProperty().getDynamicPartitionProperty().getEnd());
+        Assert.assertEquals(1, 
table.getTableProperty().getDynamicPartitionProperty().getBuckets());
+    }
+
     @Test
     public void testHourUnitWithDateType() throws AnalysisException {
         String createOlapTblStmt = "CREATE TABLE if not exists 
test.hour_with_date1 (\n"
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/catalog/TablePropertyTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/TablePropertyTest.java
new file mode 100644
index 00000000000..7cb8d46b24c
--- /dev/null
+++ b/fe/fe-core/src/test/java/org/apache/doris/catalog/TablePropertyTest.java
@@ -0,0 +1,108 @@
+// 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.
+
+package org.apache.doris.catalog;
+
+import com.google.common.collect.Maps;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Map;
+
+public class TablePropertyTest {
+
+    // A non-whitelisted dynamic_partition.* key is ignored (skipped via 
continue), so it is not
+    // collected at all and the table is neither built as dynamic nor flagged 
as incomplete.
+    @Test
+    public void testIgnoreInvalidDynamicPartitionPropertyKey() {
+        Map<String, String> properties = Maps.newHashMap();
+        
properties.put(DynamicPartitionProperty.DYNAMIC_PARTITION_PROPERTY_PREFIX + 
"not_a_real_key", "1");
+        TableProperty tableProperty = new 
TableProperty(properties).buildDynamicProperty();
+        
Assert.assertFalse(tableProperty.getDynamicPartitionProperty().isExist());
+        Assert.assertFalse(tableProperty.hasInvalidDynamicPartition());
+    }
+
+    // Only storage_medium (a leftover from a failed ALTER on a non-dynamic 
table): incomplete,
+    // must be downgraded to a non-dynamic-partition table instead of crashing 
on parseInt(null).
+    @Test
+    public void testIncompleteStorageMediumIsDowngraded() {
+        Map<String, String> properties = Maps.newHashMap();
+        properties.put(DynamicPartitionProperty.STORAGE_MEDIUM, "hdd");
+        TableProperty tableProperty = new 
TableProperty(properties).buildDynamicProperty();
+        
Assert.assertFalse(tableProperty.getDynamicPartitionProperty().isExist());
+        Assert.assertTrue(tableProperty.hasInvalidDynamicPartition());
+    }
+
+    // Symmetric to storage_medium: a leftover storage_policy alone is also 
incomplete.
+    @Test
+    public void testIncompleteStoragePolicyIsDowngraded() {
+        Map<String, String> properties = Maps.newHashMap();
+        properties.put(DynamicPartitionProperty.STORAGE_POLICY, "test_policy");
+        TableProperty tableProperty = new 
TableProperty(properties).buildDynamicProperty();
+        
Assert.assertFalse(tableProperty.getDynamicPartitionProperty().isExist());
+        Assert.assertTrue(tableProperty.hasInvalidDynamicPartition());
+    }
+
+    // time_unit present but end missing: still incomplete (covers the END 
required-key branch).
+    @Test
+    public void testIncompleteMissingEndIsDowngraded() {
+        Map<String, String> properties = Maps.newHashMap();
+        properties.put(DynamicPartitionProperty.TIME_UNIT, "DAY");
+        TableProperty tableProperty = new 
TableProperty(properties).buildDynamicProperty();
+        
Assert.assertFalse(tableProperty.getDynamicPartitionProperty().isExist());
+        Assert.assertTrue(tableProperty.hasInvalidDynamicPartition());
+    }
+
+    // time_unit + end present but prefix missing (covers the PREFIX 
required-key branch).
+    @Test
+    public void testIncompleteMissingPrefixIsDowngraded() {
+        Map<String, String> properties = Maps.newHashMap();
+        properties.put(DynamicPartitionProperty.TIME_UNIT, "DAY");
+        properties.put(DynamicPartitionProperty.END, "3");
+        TableProperty tableProperty = new 
TableProperty(properties).buildDynamicProperty();
+        
Assert.assertFalse(tableProperty.getDynamicPartitionProperty().isExist());
+        Assert.assertTrue(tableProperty.hasInvalidDynamicPartition());
+    }
+
+    // time_unit + end + prefix present but buckets missing (covers the 
BUCKETS required-key branch).
+    @Test
+    public void testIncompleteMissingBucketsIsDowngraded() {
+        Map<String, String> properties = Maps.newHashMap();
+        properties.put(DynamicPartitionProperty.TIME_UNIT, "DAY");
+        properties.put(DynamicPartitionProperty.END, "3");
+        properties.put(DynamicPartitionProperty.PREFIX, "p");
+        TableProperty tableProperty = new 
TableProperty(properties).buildDynamicProperty();
+        
Assert.assertFalse(tableProperty.getDynamicPartitionProperty().isExist());
+        Assert.assertTrue(tableProperty.hasInvalidDynamicPartition());
+    }
+
+    // All required keys present: a real DynamicPartitionProperty is built, 
not downgraded.
+    @Test
+    public void testCompleteDynamicPartitionIsBuilt() {
+        Map<String, String> properties = Maps.newHashMap();
+        properties.put(DynamicPartitionProperty.ENABLE, "true");
+        properties.put(DynamicPartitionProperty.TIME_UNIT, "DAY");
+        properties.put(DynamicPartitionProperty.END, "3");
+        properties.put(DynamicPartitionProperty.PREFIX, "p");
+        properties.put(DynamicPartitionProperty.BUCKETS, "1");
+        TableProperty tableProperty = new 
TableProperty(properties).buildDynamicProperty();
+        
Assert.assertTrue(tableProperty.getDynamicPartitionProperty().isExist());
+        Assert.assertFalse(tableProperty.hasInvalidDynamicPartition());
+        Assert.assertEquals(3, 
tableProperty.getDynamicPartitionProperty().getEnd());
+        Assert.assertEquals(1, 
tableProperty.getDynamicPartitionProperty().getBuckets());
+    }
+}


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

Reply via email to