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 f3a334eaa29c fix(flink): use safe log-reader high watermark for pre-8 
MOR incremental reads (#19949)
f3a334eaa29c is described below

commit f3a334eaa29c1ade90b04322cbecf9aa16ba6aef
Author: fhan <[email protected]>
AuthorDate: Sat Sep 19 11:53:47 2026 +0800

    fix(flink): use safe log-reader high watermark for pre-8 MOR incremental 
reads (#19949)
    
    * fix(flink): use safe log-reader high watermark for pre-8 MOR incremental 
reads
    
    ---------
    
    Co-authored-by: fhan <[email protected]>
---
 .../apache/hudi/source/IncrementalInputSplits.java | 13 +++-
 .../apache/hudi/table/format/TestInputFormat.java  | 71 ++++++++++++++++++++++
 2 files changed, 81 insertions(+), 3 deletions(-)

diff --git 
a/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/source/IncrementalInputSplits.java
 
b/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/source/IncrementalInputSplits.java
index 68e81271d6c8..6609fb5a50ab 100644
--- 
a/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/source/IncrementalInputSplits.java
+++ 
b/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/source/IncrementalInputSplits.java
@@ -24,6 +24,7 @@ import org.apache.hudi.common.model.HoodieCommitMetadata;
 import org.apache.hudi.common.model.HoodieFileGroupId;
 import org.apache.hudi.common.model.HoodieLogFile;
 import org.apache.hudi.common.table.HoodieTableMetaClient;
+import org.apache.hudi.common.table.HoodieTableVersion;
 import org.apache.hudi.common.table.cdc.HoodieCDCExtractor;
 import org.apache.hudi.common.table.cdc.HoodieCDCFileSplit;
 import org.apache.hudi.common.table.cdc.HoodieCDCUtils;
@@ -440,6 +441,8 @@ public class IncrementalInputSplits implements Serializable 
{
       InstantRange instantRange) {
     final AtomicInteger cnt = new AtomicInteger(0);
     final String mergeType = this.conf.get(FlinkOptions.MERGE_TYPE);
+    final boolean isPreV8 = metaClient.getTableConfig().getTableVersion()
+        .lesserThan(HoodieTableVersion.EIGHT);
     return fileSlices.stream().map(fileSlice -> {
       Option<List<String>> logPaths = Option.ofNullable(fileSlice.getLogFiles()
           .sorted(HoodieLogFile.getLogFileComparator())
@@ -447,9 +450,13 @@ public class IncrementalInputSplits implements 
Serializable {
           .filter(logPath -> 
!logPath.endsWith(HoodieCDCUtils.CDC_LOGFILE_SUFFIX))
           .collect(Collectors.toList()));
       String basePath = 
fileSlice.getBaseFile().map(BaseFile::getPath).orElse(null);
-      // the latest commit is used as the limit of the log reader instant 
upper threshold,
-      // it must be at least the latest instant time of the file slice to 
avoid data loss.
-      String latestCommit = 
InstantComparison.minInstant(fileSlice.getLatestInstantTime(), endInstant);
+      // Pre-v8 log file names can carry the base instant while containing 
blocks from later
+      // commits, so the filename-derived file slice instant is not a safe 
reader upper bound.
+      // Keep the existing per-split upper bound for v8+ tables. The instant 
range remains the
+      // logical query boundary for both paths.
+      String latestCommit = isPreV8
+          ? endInstant
+          : InstantComparison.minInstant(fileSlice.getLatestInstantTime(), 
endInstant);
       return new MergeOnReadInputSplit(cnt.getAndAdd(1), basePath, logPaths, 
latestCommit,
           metaClient.getBasePath().toString(), maxCompactionMemoryInBytes, 
mergeType, instantRange,
           fileSlice.getFileId(), fileSlice.getPartitionPath());
diff --git 
a/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/table/format/TestInputFormat.java
 
b/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/table/format/TestInputFormat.java
index 68dd9f8052f7..713e160a98da 100644
--- 
a/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/table/format/TestInputFormat.java
+++ 
b/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/table/format/TestInputFormat.java
@@ -118,6 +118,8 @@ import java.util.Map;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import static 
org.apache.hudi.common.table.timeline.InstantComparison.LESSER_THAN;
+import static 
org.apache.hudi.common.table.timeline.InstantComparison.compareTimestamps;
 import static 
org.apache.hudi.common.testutils.HoodieTestUtils.INSTANT_FILE_NAME_GENERATOR;
 import static org.apache.hudi.common.util.StringUtils.fromUTF8Bytes;
 import static org.apache.hudi.utils.TestData.insertRow;
@@ -984,6 +986,75 @@ public class TestInputFormat {
     TestData.assertRowDataEquals(actual6, Collections.emptyList());
   }
 
+  @Test
+  void testReadArchivedCommitsIncrementallyForPreEightMOR() throws Exception {
+    Map<String, String> options = new HashMap<>();
+    options.put(FlinkOptions.QUERY_TYPE.key(), 
FlinkOptions.QUERY_TYPE_INCREMENTAL);
+    options.put(FlinkOptions.WRITE_TABLE_VERSION.key(), 
String.valueOf(HoodieTableVersion.SIX.versionCode()));
+    options.put(HoodieTableConfig.TABLE_STORAGE_LAYOUT.key(), 
HoodieTableConfig.TableStorageLayout.DEFAULT.configValue());
+    options.put(FlinkOptions.ARCHIVE_MIN_COMMITS.key(), "3");
+    options.put(FlinkOptions.ARCHIVE_MAX_COMMITS.key(), "4");
+    options.put(FlinkOptions.CLEAN_RETAIN_COMMITS.key(), "2");
+    options.put(FlinkOptions.METADATA_ENABLED.key(), "false");
+    options.put("hoodie.commits.archival.batch", "1");
+    beforeEach(HoodieTableType.MERGE_ON_READ, options);
+
+    // The second commit updates the same pre-v8 MOR file group. Its log block 
is appended
+    // to a log file whose filename still carries the first commit instant.
+    for (int i = 1; i <= 2; i++) {
+      TestData.writeData(Collections.singletonList(insertRow(
+          StringData.fromString("id1"), StringData.fromString("Danny"), 20 + i,
+          TimestampData.fromEpochMillis(i), StringData.fromString("par1"))), 
conf);
+    }
+    // Generate unrelated commits to archive the two commits under test 
without advancing
+    // the file slice containing id1.
+    for (int i = 3; i <= 8; i++) {
+      TestData.writeData(Collections.singletonList(insertRow(
+          StringData.fromString("id" + i), StringData.fromString("User" + i), 
20 + i,
+          TimestampData.fromEpochMillis(i), StringData.fromString("par2"))), 
conf);
+    }
+
+    HoodieTableMetaClient metaClient = StreamerUtil.createMetaClient(conf);
+    assertSame(HoodieTableVersion.SIX, 
metaClient.getTableConfig().getTableVersion());
+    List<String> archivedCommits = 
metaClient.getArchivedTimeline().getCommitsTimeline().filterCompletedInstants()
+        
.getInstantsAsStream().map(HoodieInstant::requestedTime).collect(Collectors.toList());
+    assertTrue(archivedCommits.size() >= 2);
+
+    String startCommit = archivedCommits.get(0);
+    String endCommit = archivedCommits.get(1);
+    conf.set(FlinkOptions.READ_START_COMMIT, startCommit);
+    conf.set(FlinkOptions.READ_END_COMMIT, endCommit);
+    this.tableSource = getTableSource(conf);
+    InputFormat<RowData, ?> inputFormat = this.tableSource.getInputFormat();
+    assertThat(inputFormat, instanceOf(MergeOnReadInputFormat.class));
+
+    IncrementalInputSplits incrementalInputSplits = 
IncrementalInputSplits.builder()
+        .rowType(TestConfigurations.ROW_TYPE)
+        .conf(conf)
+        .path(FilePathUtils.toFlinkPath(metaClient.getBasePath()))
+        .build();
+    IncrementalInputSplits.Result splits = 
incrementalInputSplits.inputSplits(metaClient, false);
+    assertFalse(splits.isEmpty());
+    MergeOnReadInputSplit split = splits.getInputSplits().stream()
+        .filter(inputSplit -> inputSplit.getPartitionPath().equals("par1"))
+        .findFirst()
+        .orElseThrow(() -> new AssertionError("No input split found for 
par1"));
+    assertTrue(split.getLogPaths().isPresent());
+    String latestLogFileInstant = split.getLogPaths().get().stream()
+        .map(logPath -> new HoodieLogFile(new 
StoragePath(logPath)).getDeltaCommitTime())
+        .max(String::compareTo)
+        .orElseThrow(() -> new AssertionError("No log file found for par1"));
+    assertTrue(compareTimestamps(latestLogFileInstant, LESSER_THAN, endCommit),
+        "The in-range update must be newer than the filename-derived log 
instant");
+
+    List<RowData> actual = readData(inputFormat,
+        splits.getInputSplits().toArray(new MergeOnReadInputSplit[0]));
+    List<RowData> expected = Collections.singletonList(insertRow(
+        StringData.fromString("id1"), StringData.fromString("Danny"), 22,
+        TimestampData.fromEpochMillis(2), StringData.fromString("par1")));
+    TestData.assertRowDataEquals(actual, expected);
+  }
+
   @Test
   void testReadChangelogIncrementally() throws Exception {
     Map<String, String> options = new HashMap<>();

Reply via email to