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

JingsongLi 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 e4aab21645 [core] Build expired partitions from their values instead 
of a joined string (#9277)
e4aab21645 is described below

commit e4aab2164529e1baf1e71cd4c5238225d83811a0
Author: ZIHAN DAI <[email protected]>
AuthorDate: Thu Aug 20 12:02:47 2026 +1000

    [core] Build expired partitions from their values instead of a joined 
string (#9277)
---
 .../paimon/operation/NormalPartitionExpire.java    | 11 ++--
 .../paimon/operation/PartitionExpireTest.java      | 60 ++++++++++++++++++++++
 2 files changed, 66 insertions(+), 5 deletions(-)

diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/NormalPartitionExpire.java
 
b/paimon-core/src/main/java/org/apache/paimon/operation/NormalPartitionExpire.java
index 5a23f538a9..29aa8e0437 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/operation/NormalPartitionExpire.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/operation/NormalPartitionExpire.java
@@ -37,6 +37,7 @@ import java.time.Duration;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -216,12 +217,12 @@ public class NormalPartitionExpire implements 
PartitionExpire {
 
     private List<Map<String, String>> convertToPartitionString(
             List<List<String>> expiredPartValues) {
+        // Sort on the joined form, but build the partition from the original 
values: a value
+        // may itself contain DELIMITER, and splitting the joined string back 
apart would then
+        // shift every following field by one.
         return expiredPartValues.stream()
-                .map(values -> String.join(DELIMITER, values))
-                .sorted()
-                // Use split(DELIMITER, -1) to preserve trailing empty strings
-                .map(s -> s.split(DELIMITER, -1))
-                .map(strategy::toPartitionString)
+                .sorted(Comparator.comparing(values -> String.join(DELIMITER, 
values)))
+                .map(values -> strategy.toPartitionString(values.toArray()))
                 .limit(Math.min(expiredPartValues.size(), maxExpireNum))
                 .collect(Collectors.toList());
     }
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 e6abe773eb..f9dcf0d6d4 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
@@ -85,6 +85,7 @@ import static org.apache.paimon.CoreOptions.createCommitUser;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.entry;
 
 /** Test for {@link NormalPartitionExpire}. */
 public class PartitionExpireTest {
@@ -446,6 +447,65 @@ public class PartitionExpireTest {
                                 + " job to failover restart continuously. 
Expired partitions are: [20230101]");
     }
 
+    @Test
+    public void testExpirePartitionValueContainingTheDelimiter() 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.emptyMap(),
+                        ""));
+        newTable();
+        // f1 contains the delimiter that the expired partitions are joined on
+        write("20230101", "us,ca");
+        write("20230105", "51");
+
+        NormalPartitionExpire expire = newExpire();
+        expire.setLastCheck(date(1));
+        List<Map<String, String>> expired = expire.expire(date(6), 
Long.MAX_VALUE);
+
+        assertThat(expired).hasSize(1);
+        assertThat(expired.get(0)).containsExactly(entry("f0", "20230101"), 
entry("f1", "us,ca"));
+        assertThat(read()).containsExactlyInAnyOrder("20230105:51");
+    }
+
+    @Test
+    public void 
testExpireKeepsALivePartitionWhoseNeighbourContainsTheDelimiter() throws 
Exception {
+        SchemaManager schemaManager = new SchemaManager(LocalFileIO.create(), 
path);
+        schemaManager.createTable(
+                new Schema(
+                        RowType.of(VarCharType.STRING_TYPE, 
VarCharType.STRING_TYPE).getFields(),
+                        singletonList("f0"),
+                        emptyList(),
+                        Collections.emptyMap(),
+                        ""));
+        newTable();
+        Map<String, String> options = new HashMap<>();
+        options.put(CoreOptions.PARTITION_EXPIRATION_STRATEGY.key(), 
"update-time");
+        options.put(PARTITION_EXPIRATION_TIME.key(), "1 s");
+        options.put(PARTITION_EXPIRATION_CHECK_INTERVAL.key(), "1 d");
+        table = table.copy(options);
+
+        // "us,ca" is old enough to expire; "us" is written afterwards and 
must survive. Under
+        // update-time any partition value is accepted, so the delimiter 
reaches the round trip.
+        write("us,ca", "old");
+        Thread.sleep(2000);
+        write("us", "fresh");
+        // pin the check time now, so a later stall cannot move the cut-off 
past "us"
+        LocalDateTime checkTime = LocalDateTime.now();
+
+        NormalPartitionExpire expire =
+                (NormalPartitionExpire) table.store().newPartitionExpire("", 
table);
+        expire.setLastCheck(checkTime.minusDays(2));
+        List<Map<String, String>> expired = expire.expire(checkTime, 
Long.MAX_VALUE);
+
+        assertThat(expired).hasSize(1);
+        assertThat(expired.get(0)).containsExactly(entry("f0", "us,ca"));
+        assertThat(read()).containsExactlyInAnyOrder("us:fresh");
+    }
+
     private List<String> read() throws IOException {
         List<String> ret = new ArrayList<>();
         table.newRead()

Reply via email to