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]