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

lzljs3620320 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new 13072be478 [core] fix NPE and ArrayIndexOutOfBoundsException for 
PartitionExpire (#6150)
13072be478 is described below

commit 13072be47864460b572853e8fcddb29f61418e5b
Author: LsomeYeah <94825748+lsomey...@users.noreply.github.com>
AuthorDate: Tue Aug 26 19:27:37 2025 +0800

    [core] fix NPE and ArrayIndexOutOfBoundsException for PartitionExpire 
(#6150)
---
 .../apache/paimon/operation/PartitionExpire.java   |  3 ++-
 .../paimon/partition/PartitionExpireStrategy.java  | 14 ++++++++---
 .../partition/PartitionExpireStrategyFactory.java  |  6 ++++-
 .../PartitionUpdateTimeExpireStrategy.java         |  5 ++--
 .../PartitionValuesTimeExpireStrategy.java         |  2 +-
 .../paimon/operation/PartitionExpireTest.java      | 29 ++++++++++++++++++++++
 .../CustomPartitionExpirationFactory.java          |  8 ++++--
 7 files changed, 56 insertions(+), 11 deletions(-)

diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/PartitionExpire.java 
b/paimon-core/src/main/java/org/apache/paimon/operation/PartitionExpire.java
index 7819063f2e..36d6976152 100644
--- a/paimon-core/src/main/java/org/apache/paimon/operation/PartitionExpire.java
+++ b/paimon-core/src/main/java/org/apache/paimon/operation/PartitionExpire.java
@@ -185,7 +185,8 @@ public class PartitionExpire {
         return expiredPartValues.stream()
                 .map(values -> String.join(DELIMITER, values))
                 .sorted()
-                .map(s -> s.split(DELIMITER))
+                // Use split(DELIMITER, -1) to preserve trailing empty strings
+                .map(s -> s.split(DELIMITER, -1))
                 .map(strategy::toPartitionString)
                 .limit(Math.min(expiredPartValues.size(), maxExpireNum))
                 .collect(Collectors.toList());
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/partition/PartitionExpireStrategy.java
 
b/paimon-core/src/main/java/org/apache/paimon/partition/PartitionExpireStrategy.java
index 7d016c0931..ce021eb1d3 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/partition/PartitionExpireStrategy.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/partition/PartitionExpireStrategy.java
@@ -39,11 +39,13 @@ import java.util.Map;
 public abstract class PartitionExpireStrategy {
 
     protected final List<String> partitionKeys;
+    protected final String partitionDefaultName;
     private final RowDataToObjectArrayConverter toObjectArrayConverter;
 
-    public PartitionExpireStrategy(RowType partitionType) {
+    public PartitionExpireStrategy(RowType partitionType, String 
partitionDefaultName) {
         this.toObjectArrayConverter = new 
RowDataToObjectArrayConverter(partitionType);
         this.partitionKeys = partitionType.getFieldNames();
+        this.partitionDefaultName = partitionDefaultName;
     }
 
     public Map<String, String> toPartitionString(Object[] array) {
@@ -57,7 +59,11 @@ public abstract class PartitionExpireStrategy {
     public List<String> toPartitionValue(Object[] array) {
         List<String> list = new ArrayList<>(partitionKeys.size());
         for (int i = 0; i < partitionKeys.size(); i++) {
-            list.add(array[i].toString());
+            if (array[i] != null) {
+                list.add(array[i].toString());
+            } else {
+                list.add(partitionDefaultName);
+            }
         }
         return list;
     }
@@ -76,13 +82,13 @@ public abstract class PartitionExpireStrategy {
             @Nullable Identifier identifier) {
         switch (options.partitionExpireStrategy()) {
             case UPDATE_TIME:
-                return new PartitionUpdateTimeExpireStrategy(partitionType);
+                return new PartitionUpdateTimeExpireStrategy(options, 
partitionType);
             case VALUES_TIME:
                 return new PartitionValuesTimeExpireStrategy(options, 
partitionType);
             case CUSTOM:
                 return PartitionExpireStrategyFactory.INSTANCE
                         .get()
-                        .create(catalogLoader, identifier, partitionType);
+                        .create(catalogLoader, identifier, options, 
partitionType);
             default:
                 throw new IllegalArgumentException(
                         "Unknown partitionExpireStrategy: " + 
options.partitionExpireStrategy());
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/partition/PartitionExpireStrategyFactory.java
 
b/paimon-core/src/main/java/org/apache/paimon/partition/PartitionExpireStrategyFactory.java
index 6e89e98725..d871e4cfd2 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/partition/PartitionExpireStrategyFactory.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/partition/PartitionExpireStrategyFactory.java
@@ -18,6 +18,7 @@
 
 package org.apache.paimon.partition;
 
+import org.apache.paimon.CoreOptions;
 import org.apache.paimon.catalog.CatalogLoader;
 import org.apache.paimon.catalog.Identifier;
 import org.apache.paimon.factories.FactoryUtil;
@@ -30,7 +31,10 @@ import 
org.apache.paimon.shade.guava30.com.google.common.base.Suppliers;
 public interface PartitionExpireStrategyFactory {
 
     PartitionExpireStrategy create(
-            CatalogLoader catalogLoader, Identifier identifier, RowType 
partitionType);
+            CatalogLoader catalogLoader,
+            Identifier identifier,
+            CoreOptions options,
+            RowType partitionType);
 
     Supplier<PartitionExpireStrategyFactory> INSTANCE =
             Suppliers.memoize(
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/partition/PartitionUpdateTimeExpireStrategy.java
 
b/paimon-core/src/main/java/org/apache/paimon/partition/PartitionUpdateTimeExpireStrategy.java
index c2d75e8e74..3cb7a405d2 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/partition/PartitionUpdateTimeExpireStrategy.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/partition/PartitionUpdateTimeExpireStrategy.java
@@ -18,6 +18,7 @@
 
 package org.apache.paimon.partition;
 
+import org.apache.paimon.CoreOptions;
 import org.apache.paimon.manifest.PartitionEntry;
 import org.apache.paimon.operation.FileStoreScan;
 import org.apache.paimon.types.RowType;
@@ -33,8 +34,8 @@ import java.util.stream.Collectors;
  */
 public class PartitionUpdateTimeExpireStrategy extends PartitionExpireStrategy 
{
 
-    public PartitionUpdateTimeExpireStrategy(RowType partitionType) {
-        super(partitionType);
+    public PartitionUpdateTimeExpireStrategy(CoreOptions options, RowType 
partitionType) {
+        super(partitionType, options.partitionDefaultName());
     }
 
     @Override
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/partition/PartitionValuesTimeExpireStrategy.java
 
b/paimon-core/src/main/java/org/apache/paimon/partition/PartitionValuesTimeExpireStrategy.java
index 51c53282c4..70c55cfb38 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/partition/PartitionValuesTimeExpireStrategy.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/partition/PartitionValuesTimeExpireStrategy.java
@@ -48,7 +48,7 @@ public class PartitionValuesTimeExpireStrategy extends 
PartitionExpireStrategy {
     private final PartitionTimeExtractor timeExtractor;
 
     public PartitionValuesTimeExpireStrategy(CoreOptions options, RowType 
partitionType) {
-        super(partitionType);
+        super(partitionType, options.partitionDefaultName());
         String timePattern = options.partitionTimestampPattern();
         String timeFormatter = options.partitionTimestampFormatter();
         this.timeExtractor = new PartitionTimeExtractor(timePattern, 
timeFormatter);
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/operation/PartitionExpireTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/operation/PartitionExpireTest.java
index 2b45ad4352..4c48671add 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/operation/PartitionExpireTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/operation/PartitionExpireTest.java
@@ -61,6 +61,7 @@ import java.time.LocalTime;
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
@@ -217,6 +218,34 @@ public class PartitionExpireTest {
         assertThat(overwriteSnapshotCnt).isEqualTo(3L);
     }
 
+    @Test
+    public void testExpireWithNullOrEmptyPartition() throws Exception {
+        SchemaManager schemaManager = new SchemaManager(LocalFileIO.create(), 
path);
+        schemaManager.createTable(
+                new Schema(
+                        RowType.of(VarCharType.STRING_TYPE, 
VarCharType.STRING_TYPE).getFields(),
+                        Arrays.asList("f0", "f1"),
+                        emptyList(),
+                        
Collections.singletonMap(METASTORE_PARTITIONED_TABLE.key(), "true"),
+                        ""));
+        newTable();
+        write("20230101", "11");
+        write("20230101", "12");
+        // sub partition is null
+        write("20230101", null);
+        // sub partition is empty string
+        write("20230103", "");
+        write("20230103", "32");
+        write("20230105", "51");
+
+        PartitionExpire expire = newExpire();
+        expire.setLastCheck(date(1));
+        Assertions.assertDoesNotThrow(() -> expire.expire(date(6), 
Long.MAX_VALUE));
+
+        // null partition and empty string partition should be expired
+        assertThat(read()).containsExactlyInAnyOrder("20230105:51");
+    }
+
     @Test
     public void test() throws Exception {
         SchemaManager schemaManager = new SchemaManager(LocalFileIO.create(), 
path);
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/partition/CustomPartitionExpirationFactory.java
 
b/paimon-core/src/test/java/org/apache/paimon/partition/CustomPartitionExpirationFactory.java
index 4f2b770d55..9fa6369f93 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/partition/CustomPartitionExpirationFactory.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/partition/CustomPartitionExpirationFactory.java
@@ -18,6 +18,7 @@
 
 package org.apache.paimon.partition;
 
+import org.apache.paimon.CoreOptions;
 import org.apache.paimon.catalog.Catalog;
 import org.apache.paimon.catalog.CatalogLoader;
 import org.apache.paimon.catalog.Identifier;
@@ -38,8 +39,11 @@ public class CustomPartitionExpirationFactory implements 
PartitionExpireStrategy
 
     @Override
     public PartitionExpireStrategy create(
-            CatalogLoader catalogLoader, Identifier identifier, RowType 
partitionType) {
-        return new PartitionExpireStrategy(partitionType) {
+            CatalogLoader catalogLoader,
+            Identifier identifier,
+            CoreOptions options,
+            RowType partitionType) {
+        return new PartitionExpireStrategy(partitionType, 
options.partitionDefaultName()) {
             @Override
             public List<PartitionEntry> selectExpiredPartitions(
                     FileStoreScan scan, LocalDateTime expirationTime) {

Reply via email to