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

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


The following commit(s) were added to refs/heads/master by this push:
     new aff607b1c975 fix: fix get empty completion time in corner case (#14379)
aff607b1c975 is described below

commit aff607b1c9756c10715afa98c8fa0c599225924a
Author: TheR1sing3un <[email protected]>
AuthorDate: Thu Nov 27 18:10:51 2025 +0800

    fix: fix get empty completion time in corner case (#14379)
    
    the corner case: the load instant range is contained within one of the 
archived file instant range.
    
    ---------
    
    Signed-off-by: TheR1sing3un <[email protected]>
    Co-authored-by: danny0405 <[email protected]>
---
 .../timeline/TestCompletionTimeQueryView.java      | 47 ++++++++++++++++++++--
 .../table/timeline/HoodieArchivedTimeline.java     | 13 ++++++
 .../hudi/common/table/timeline/LSMTimeline.java    |  2 +-
 3 files changed, 57 insertions(+), 5 deletions(-)

diff --git 
a/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/client/timeline/TestCompletionTimeQueryView.java
 
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/client/timeline/TestCompletionTimeQueryView.java
index 5e5434644c16..8b77c1459fc2 100644
--- 
a/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/client/timeline/TestCompletionTimeQueryView.java
+++ 
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/client/timeline/TestCompletionTimeQueryView.java
@@ -53,6 +53,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
 
 import static 
org.apache.hudi.common.testutils.HoodieTestUtils.INSTANT_FILE_NAME_GENERATOR;
@@ -105,6 +106,27 @@ public class TestCompletionTimeQueryView {
     }
   }
 
+  @Test
+  void testReadCompletionTimeWithCornerCase() throws Exception {
+    String tableName = "testTable";
+    String tablePath = tempFile.getAbsolutePath() + StoragePath.SEPARATOR + 
tableName;
+    HoodieTableMetaClient metaClient = HoodieTestUtils.init(
+        HoodieTestUtils.getDefaultStorageConf(), tablePath, 
HoodieTableType.COPY_ON_WRITE, tableName);
+    prepareTimeline(tablePath, metaClient, (lsmTimelineWriter, activeActions) 
-> {
+      // archive [1, 2], [3, 6] specifically to create corner cases
+      lsmTimelineWriter.write(activeActions.subList(0, 2), Option.empty(), 
Option.empty());
+      lsmTimelineWriter.write(activeActions.subList(2, 6), Option.empty(), 
Option.empty());
+    });
+    try (CompletionTimeQueryView view =
+             
metaClient.getTableFormat().getTimelineFactory().createCompletionTimeQueryView(metaClient))
 {
+      // 1. first time, we try to load archived instant from 5, so the cursor 
will move forward from 7 to 5
+      assertThat(view.getCompletionTime(String.format("%08d", 5)).orElse(""), 
is(String.format("%08d", 1005)));
+
+      // 2. then we try to load archived instant from 4, it should get the 
completion time correctly
+      assertThat(view.getCompletionTime(String.format("%08d", 4)).orElse(""), 
is(String.format("%08d", 1004)));
+    }
+  }
+
   @Test
   void testReadStartTime() throws Exception {
     String tableName = "testTable";
@@ -180,6 +202,16 @@ public class TestCompletionTimeQueryView {
   }
 
   private void prepareTimeline(String tablePath, HoodieTableMetaClient 
metaClient, List<Pair<String, String>> instantRequestedAndCompletionTime, 
String requestedInstantTime) throws Exception {
+    prepareTimeline(tablePath, metaClient, instantRequestedAndCompletionTime, 
requestedInstantTime, (writer, activeActions) -> {
+      // archive [1,2], [3,4], [5,6] separately
+      writer.write(activeActions.subList(0, 2), Option.empty(), 
Option.empty());
+      writer.write(activeActions.subList(2, 4), Option.empty(), 
Option.empty());
+      writer.write(activeActions.subList(4, 6), Option.empty(), 
Option.empty());
+    });
+  }
+
+  private void prepareTimeline(String tablePath, HoodieTableMetaClient 
metaClient, List<Pair<String, String>> instantRequestedAndCompletionTime, 
String requestedInstantTime,
+                               BiConsumer<LSMTimelineWriter, 
List<ActiveAction>> timelineWriterListBiConsumer) throws Exception {
     HoodieWriteConfig writeConfig = 
HoodieWriteConfig.newBuilder().withPath(tablePath)
         
.withIndexConfig(HoodieIndexConfig.newBuilder().withIndexType(HoodieIndex.IndexType.INMEMORY).build())
         .withMarkersType("DIRECT")
@@ -199,10 +231,7 @@ public class TestCompletionTimeQueryView {
     testTable.addRequestedCommit(requestedInstantTime);
     List<HoodieInstant> instants = 
TIMELINE_FACTORY.createActiveTimeline(metaClient, 
false).getInstantsAsStream().sorted().collect(Collectors.toList());
     LSMTimelineWriter writer = LSMTimelineWriter.getInstance(writeConfig, 
getMockHoodieTable(metaClient));
-    // archive [1,2], [3,4], [5,6] separately
-    writer.write(activeActions.subList(0, 2), Option.empty(), Option.empty());
-    writer.write(activeActions.subList(2, 4), Option.empty(), Option.empty());
-    writer.write(activeActions.subList(4, 6), Option.empty(), Option.empty());
+    timelineWriterListBiConsumer.accept(writer, activeActions);
     // reconcile the active timeline
     instants.subList(0, 3 * 6).forEach(
         instant -> TimelineUtils.deleteInstantFile(metaClient.getStorage(),
@@ -222,6 +251,16 @@ public class TestCompletionTimeQueryView {
     prepareTimeline(tablePath, metaClient, instantRequestedAndCompletionTime, 
String.format("%08d", 11));
   }
 
+  private void prepareTimeline(String tablePath, HoodieTableMetaClient 
metaClient, BiConsumer<LSMTimelineWriter, List<ActiveAction>> 
timelineWriterListBiConsumer) throws Exception {
+    List<Pair<String, String>> instantRequestedAndCompletionTime = new 
ArrayList<>();
+    for (int i = 1; i < 11; i++) {
+      String instantTime = String.format("%08d", i);
+      String completionTime = String.format("%08d", i + 1000);
+      instantRequestedAndCompletionTime.add(Pair.of(instantTime, 
completionTime));
+    }
+    prepareTimeline(tablePath, metaClient, instantRequestedAndCompletionTime, 
String.format("%08d", 11), timelineWriterListBiConsumer);
+  }
+
   @SuppressWarnings("rawtypes")
   private HoodieTable getMockHoodieTable(HoodieTableMetaClient metaClient) {
     HoodieTable hoodieTable = mock(HoodieTable.class);
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/table/timeline/HoodieArchivedTimeline.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/table/timeline/HoodieArchivedTimeline.java
index 5c79864fe5ae..6393f417b22e 100644
--- 
a/hudi-common/src/main/java/org/apache/hudi/common/table/timeline/HoodieArchivedTimeline.java
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/table/timeline/HoodieArchivedTimeline.java
@@ -82,6 +82,19 @@ public interface HoodieArchivedTimeline extends 
HoodieTimeline {
     public boolean isInRange(String instantTime) {
       return InstantComparison.isInRange(instantTime, this.startTs, 
this.endTs);
     }
+
+    /**
+     * Returns whether the given instant time range has overlapping with the 
current range.
+     */
+    public boolean hasOverlappingInRange(String startInstant, String 
endInstant) {
+      return isInRange(startInstant) || isInRange(endInstant) || 
isContained(startInstant, endInstant);
+    }
+
+    private boolean isContained(String startInstant, String endInstant) {
+      // the given range is finite and expected to be covered by the current 
range which naturally cannot be infinite.
+      return startTs != null && InstantComparison.compareTimestamps(startTs, 
InstantComparison.GREATER_THAN_OR_EQUALS, startInstant)
+          && endTs != null && InstantComparison.compareTimestamps(endTs, 
InstantComparison.LESSER_THAN_OR_EQUALS, endInstant);
+    }
   }
 
   /**
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/table/timeline/LSMTimeline.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/table/timeline/LSMTimeline.java
index 2c06bdcd010d..c99dcb67f0bd 100644
--- 
a/hudi-common/src/main/java/org/apache/hudi/common/table/timeline/LSMTimeline.java
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/table/timeline/LSMTimeline.java
@@ -142,7 +142,7 @@ public class LSMTimeline {
   public static boolean isFileInRange(HoodieArchivedTimeline.TimeRangeFilter 
filter, String fileName) {
     String minInstant = getMinInstantTime(fileName);
     String maxInstant = getMaxInstantTime(fileName);
-    return filter.isInRange(minInstant) || filter.isInRange(maxInstant);
+    return filter.hasOverlappingInRange(minInstant, maxInstant);
   }
 
   /**

Reply via email to