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 d9e44c5c519a feat(reader): Adapt the HoodieFileGroupReader to read the 
native format log files (#19072)
d9e44c5c519a is described below

commit d9e44c5c519a97031e4bb665190e8a5c2eb79ee7
Author: Shuo Cheng <[email protected]>
AuthorDate: Tue Jun 30 13:46:31 2026 +0800

    feat(reader): Adapt the HoodieFileGroupReader to read the native format log 
files (#19072)
---
 .../hudi/io/HoodieNativeLogFormatWriter.java       |  20 +-
 .../java/org/apache/hudi/common/fs/FSUtils.java    |  13 +-
 .../hudi/common/table/TableSchemaResolver.java     |   7 +
 .../table/log/BaseHoodieLogRecordReader.java       |   2 +-
 .../common/table/log/HoodieLogFormatReader.java    |  37 ++-
 .../table/log/HoodieNativeLogFileReader.java       | 139 +++++++++
 .../common/table/log/NativeLogFooterMetadata.java  | 171 +++++++++++
 .../common/table/log/block/HoodieDataBlock.java    |   2 +-
 .../common/table/log/block/HoodieDeleteBlock.java  |  12 +
 .../table/log/block/HoodieNativeDataBlock.java     | 133 ++++++++
 .../table/log/block/HoodieNativeDeleteBlock.java   | 106 +++++++
 .../common/table/read/BufferedRecordMerger.java    |   4 +
 .../table/read/BufferedRecordMergerFactory.java    |  22 ++
 .../read/buffer/HoodieFileGroupRecordBuffer.java   |   6 -
 .../read/buffer/KeyBasedFileGroupRecordBuffer.java |  18 +-
 .../buffer/PositionBasedFileGroupRecordBuffer.java |  16 +-
 .../read/buffer/ReusableKeyBasedRecordBuffer.java  |   6 -
 .../read/buffer/UnmergedFileGroupRecordBuffer.java |   6 -
 .../table/view/AbstractTableFileSystemView.java    |   6 +-
 .../table/log/TestHoodieNativeLogFileReader.java   |  51 ++++
 .../table/log/TestNativeLogFooterMetadata.java     |  94 ++++++
 .../read/buffer/BaseTestFileGroupRecordBuffer.java |  13 +
 .../buffer/TestKeyBasedFileGroupRecordBuffer.java  |   7 +-
 .../TestSortedKeyBasedFileGroupRecordBuffer.java   |   6 +-
 .../hudi/common/table/TestTableSchemaResolver.java |  69 +++++
 .../read/TestHoodieFileGroupReaderNativeLogs.java  | 338 +++++++++++++++++++++
 .../table/view/TestHoodieTableFileSystemView.java  |  32 ++
 .../reader/HoodieFileGroupReaderTestHarness.java   |   7 +-
 .../testutils/reader/HoodieFileSliceTestUtils.java | 187 ++++++++++++
 29 files changed, 1446 insertions(+), 84 deletions(-)

diff --git 
a/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/HoodieNativeLogFormatWriter.java
 
b/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/HoodieNativeLogFormatWriter.java
index 8c7867ec7ea8..1463b3b59f7a 100644
--- 
a/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/HoodieNativeLogFormatWriter.java
+++ 
b/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/HoodieNativeLogFormatWriter.java
@@ -30,11 +30,11 @@ import org.apache.hudi.common.table.HoodieTableVersion;
 import org.apache.hudi.common.table.log.AppendResult;
 import org.apache.hudi.common.table.log.HoodieLogFormat;
 import org.apache.hudi.common.table.log.LogFileCreationCallback;
+import org.apache.hudi.common.table.log.NativeLogFooterMetadata;
 import org.apache.hudi.common.table.log.block.HoodieLogBlock;
 import 
org.apache.hudi.common.table.log.block.HoodieLogBlock.HeaderMetadataType;
 import org.apache.hudi.common.table.read.BufferedRecord;
 import org.apache.hudi.common.table.read.BufferedRecords;
-import org.apache.hudi.common.util.JsonUtils;
 import org.apache.hudi.common.util.Option;
 import org.apache.hudi.common.util.OrderingValues;
 import org.apache.hudi.common.util.collection.ArrayComparable;
@@ -47,8 +47,6 @@ import org.apache.hudi.storage.StoragePath;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
@@ -61,8 +59,6 @@ import static 
org.apache.hudi.common.model.LogExtensions.DELETE_LOG_EXTENSION;
  */
 public class HoodieNativeLogFormatWriter extends HoodieLogFormat.Writer {
 
-  private static final String LOG_FORMAT_METADATA_FOOTER_KEY = 
"hudi.log.format.metadata";
-
   private final HoodieWriteConfig writeConfig;
   private final HoodieFileFormat nativeFileFormat;
   private final HoodieSchema tableSchema;
@@ -215,22 +211,10 @@ public class HoodieNativeLogFormatWriter extends 
HoodieLogFormat.Writer {
 
   private void addFooterMetadata(Map<HeaderMetadataType, String> header) 
throws IOException {
     if (dataFileWriter != null) {
-      dataFileWriter.addFooterMetadata(getFooterMetadata(header));
+      
dataFileWriter.addFooterMetadata(NativeLogFooterMetadata.toFooterMetadata(header));
     }
   }
 
-  private Map<String, String> getFooterMetadata(Map<HeaderMetadataType, 
String> header) throws IOException {
-    Map<String, String> logFormatMetadata = new LinkedHashMap<>();
-    logFormatMetadata.put(HeaderMetadataType.VERSION.name(), 
String.valueOf(HoodieLogFormat.CURRENT_VERSION));
-    header.forEach((key, value) -> {
-      if (value != null) {
-        logFormatMetadata.put(key.name(), value);
-      }
-    });
-    return Collections.singletonMap(
-        LOG_FORMAT_METADATA_FOOTER_KEY, 
JsonUtils.getObjectMapper().writeValueAsString(logFormatMetadata));
-  }
-
   private void ensureDataFileWriter(HoodieSchema recordSchema) throws 
IOException {
     ensureAppendVersion();
     if (dataFileWriter == null) {
diff --git a/hudi-common/src/main/java/org/apache/hudi/common/fs/FSUtils.java 
b/hudi-common/src/main/java/org/apache/hudi/common/fs/FSUtils.java
index 7b5d0b7002ee..ff39c9f23358 100644
--- a/hudi-common/src/main/java/org/apache/hudi/common/fs/FSUtils.java
+++ b/hudi-common/src/main/java/org/apache/hudi/common/fs/FSUtils.java
@@ -865,7 +865,18 @@ public class FSUtils {
   private static Option<HoodieLogFile> getLatestLogFile(Stream<HoodieLogFile> 
logFiles) {
     return 
Option.fromJavaOptional(logFiles.min(HoodieLogFile.getReverseLogFileComparator()));
   }
-  
+
+  /**
+   * Return the file size of a log file.
+   */
+  public static long getFileSize(HoodieStorage storage, HoodieLogFile logFile) 
{
+    try {
+      return logFile.getFileSize() >= 0 ? logFile.getFileSize() : 
storage.getPathInfo(logFile.getPath()).getLength();
+    } catch (IOException e) {
+      throw new HoodieIOException("Unable to get file size for " + logFile, e);
+    }
+  }
+
   public static Map<String, Boolean> deleteFilesParallelize(
       HoodieTableMetaClient metaClient,
       List<String> paths,
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/table/TableSchemaResolver.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/table/TableSchemaResolver.java
index 157f9d4c79ca..2b98896fe6c7 100644
--- 
a/hudi-common/src/main/java/org/apache/hudi/common/table/TableSchemaResolver.java
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/table/TableSchemaResolver.java
@@ -29,6 +29,7 @@ import org.apache.hudi.common.schema.HoodieSchemaType;
 import org.apache.hudi.common.schema.HoodieSchemaUtils;
 import org.apache.hudi.common.table.log.HoodieLogFormat;
 import org.apache.hudi.common.table.log.HoodieLogFormat.Reader;
+import org.apache.hudi.common.table.log.NativeLogFooterMetadata;
 import org.apache.hudi.common.table.log.block.HoodieDataBlock;
 import org.apache.hudi.common.table.log.block.HoodieLogBlock;
 import org.apache.hudi.common.table.timeline.HoodieInstant;
@@ -61,6 +62,7 @@ import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Supplier;
+import java.util.regex.Matcher;
 import java.util.stream.Stream;
 
 /**
@@ -286,6 +288,11 @@ public class TableSchemaResolver {
    * @return
    */
   public static HoodieSchema readSchemaFromLogFile(HoodieStorage storage, 
StoragePath path) throws IOException {
+    Option<Matcher> nativeLogMatcherOpt = 
FSUtils.matchNativeLogFile(path.getName());
+    if (nativeLogMatcherOpt.isPresent()) {
+      return NativeLogFooterMetadata.readSchemaFromNativeLogFile(storage, 
path, nativeLogMatcherOpt.get());
+    }
+
     // We only need to read the schema from the log block header,
     // so we read the block lazily to avoid reading block content
     // containing the records
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/table/log/BaseHoodieLogRecordReader.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/table/log/BaseHoodieLogRecordReader.java
index 159662f73bb9..4fbb1cf8dfc0 100644
--- 
a/hudi-common/src/main/java/org/apache/hudi/common/table/log/BaseHoodieLogRecordReader.java
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/table/log/BaseHoodieLogRecordReader.java
@@ -226,7 +226,7 @@ public abstract class BaseHoodieLogRecordReader<T> {
     HoodieLogFormatReader logFormatReaderWrapper = null;
     try {
       // Iterate over the paths
-      logFormatReaderWrapper = new HoodieLogFormatReader(storage, logFiles,
+      logFormatReaderWrapper = new HoodieLogFormatReader(storage, 
readerContext, hoodieTableMetaClient, logFiles,
           readerSchema, reverseReader, bufferSize, shouldLookupRecords(), 
recordKeyField, internalSchema);
 
       /**
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/table/log/HoodieLogFormatReader.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/table/log/HoodieLogFormatReader.java
index 1c342fecf39c..d501cbc4a1f7 100644
--- 
a/hudi-common/src/main/java/org/apache/hudi/common/table/log/HoodieLogFormatReader.java
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/table/log/HoodieLogFormatReader.java
@@ -18,10 +18,15 @@
 
 package org.apache.hudi.common.table.log;
 
+import org.apache.hudi.common.engine.HoodieReaderContext;
+import org.apache.hudi.common.fs.FSUtils;
 import org.apache.hudi.common.model.HoodieLogFile;
 import org.apache.hudi.common.schema.HoodieSchema;
+import org.apache.hudi.common.table.HoodieTableMetaClient;
 import org.apache.hudi.common.table.log.block.HoodieLogBlock;
+import org.apache.hudi.common.util.HoodieRecordUtils;
 import org.apache.hudi.exception.HoodieIOException;
+import org.apache.hudi.exception.HoodieNotSupportedException;
 import org.apache.hudi.internal.schema.InternalSchema;
 import org.apache.hudi.storage.HoodieStorage;
 
@@ -37,10 +42,12 @@ import java.util.List;
 public class HoodieLogFormatReader implements HoodieLogFormat.Reader {
 
   private final List<HoodieLogFile> logFiles;
-  private HoodieLogFileReader currentReader;
+  private HoodieLogFormat.Reader currentReader;
   private final HoodieStorage storage;
   private final HoodieSchema readerSchema;
   private final InternalSchema internalSchema;
+  private final HoodieReaderContext<?> readerContext;
+  private final HoodieTableMetaClient metaClient;
   private final String recordKeyField;
   private final boolean enableInlineReading;
   private final int bufferSize;
@@ -48,8 +55,18 @@ public class HoodieLogFormatReader implements 
HoodieLogFormat.Reader {
   HoodieLogFormatReader(HoodieStorage storage, List<HoodieLogFile> logFiles, 
HoodieSchema readerSchema,
                         boolean reverseLogReader, int bufferSize, boolean 
enableRecordLookups,
                         String recordKeyField, InternalSchema internalSchema) 
throws IOException {
+    this(storage, null, null, logFiles, readerSchema, reverseLogReader, 
bufferSize, enableRecordLookups,
+        recordKeyField, internalSchema);
+  }
+
+  HoodieLogFormatReader(HoodieStorage storage, HoodieReaderContext<?> 
readerContext, HoodieTableMetaClient metaClient,
+                        List<HoodieLogFile> logFiles, HoodieSchema 
readerSchema,
+                        boolean reverseLogReader, int bufferSize, boolean 
enableRecordLookups,
+                        String recordKeyField, InternalSchema internalSchema) 
throws IOException {
     this.logFiles = logFiles;
     this.storage = storage;
+    this.readerContext = readerContext;
+    this.metaClient = metaClient;
     this.readerSchema = readerSchema;
     this.bufferSize = bufferSize;
     this.recordKeyField = recordKeyField;
@@ -57,8 +74,7 @@ public class HoodieLogFormatReader implements 
HoodieLogFormat.Reader {
     this.internalSchema = internalSchema == null ? 
InternalSchema.getEmptyInternalSchema() : internalSchema;
     if (!logFiles.isEmpty()) {
       HoodieLogFile nextLogFile = logFiles.remove(0);
-      this.currentReader = new HoodieLogFileReader(storage, nextLogFile, 
readerSchema, bufferSize, false,
-          enableRecordLookups, recordKeyField, internalSchema);
+      this.currentReader = createReader(nextLogFile, reverseLogReader);
     }
   }
 
@@ -84,8 +100,7 @@ public class HoodieLogFormatReader implements 
HoodieLogFormat.Reader {
       try {
         HoodieLogFile nextLogFile = logFiles.remove(0);
         this.currentReader.close();
-        this.currentReader = new HoodieLogFileReader(storage, nextLogFile, 
readerSchema, bufferSize, false,
-            enableInlineReading, recordKeyField, internalSchema);
+        this.currentReader = createReader(nextLogFile, false);
       } catch (IOException io) {
         throw new HoodieIOException("unable to initialize read with log file 
", io);
       }
@@ -118,4 +133,16 @@ public class HoodieLogFormatReader implements 
HoodieLogFormat.Reader {
   public HoodieLogBlock prev() throws IOException {
     return this.currentReader.prev();
   }
+
+  private HoodieLogFormat.Reader createReader(HoodieLogFile logFile, boolean 
reverseLogReader) throws IOException {
+    if (FSUtils.matchNativeLogFile(logFile.getFileName()).isPresent()) {
+      if (readerContext == null || metaClient == null) {
+        throw new HoodieNotSupportedException("Native log files require 
HoodieFileGroupReader based reader context");
+      }
+      return new HoodieNativeLogFileReader(storage, logFile, readerContext, 
readerSchema,
+          
HoodieRecordUtils.getOrderingFieldNames(readerContext.getMergeMode(), 
metaClient));
+    }
+    return new HoodieLogFileReader(storage, logFile, readerSchema, bufferSize, 
reverseLogReader,
+        enableInlineReading, recordKeyField, internalSchema);
+  }
 }
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/table/log/HoodieNativeLogFileReader.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/table/log/HoodieNativeLogFileReader.java
new file mode 100644
index 000000000000..cbb7a6b514c4
--- /dev/null
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/table/log/HoodieNativeLogFileReader.java
@@ -0,0 +1,139 @@
+/*
+ * 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.hudi.common.table.log;
+
+import org.apache.hudi.common.engine.HoodieReaderContext;
+import org.apache.hudi.common.fs.FSUtils;
+import org.apache.hudi.common.model.HoodieFileFormat;
+import org.apache.hudi.common.model.HoodieLogFile;
+import org.apache.hudi.common.schema.HoodieSchema;
+import org.apache.hudi.common.schema.HoodieSchemas;
+import org.apache.hudi.common.table.log.block.HoodieLogBlock;
+import 
org.apache.hudi.common.table.log.block.HoodieLogBlock.HeaderMetadataType;
+import org.apache.hudi.common.table.log.block.HoodieNativeDataBlock;
+import org.apache.hudi.common.table.log.block.HoodieNativeDeleteBlock;
+import org.apache.hudi.common.util.Option;
+import org.apache.hudi.exception.HoodieIOException;
+import org.apache.hudi.exception.HoodieNotSupportedException;
+import org.apache.hudi.io.storage.HoodieIOFactory;
+import org.apache.hudi.storage.HoodieStorage;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+/**
+ * Reader for native log files.
+ */
+public class HoodieNativeLogFileReader implements HoodieLogFormat.Reader {
+
+  private final HoodieStorage storage;
+  private final HoodieLogFile logFile;
+  private final HoodieReaderContext<?> readerContext;
+  private final HoodieSchema readerSchema;
+  private final List<String> orderingFieldNames;
+  private boolean consumed;
+
+  HoodieNativeLogFileReader(HoodieStorage storage, HoodieLogFile logFile, 
HoodieReaderContext<?> readerContext,
+                            HoodieSchema readerSchema, List<String> 
orderingFieldNames) {
+    this.storage = storage;
+    this.logFile = logFile;
+    this.readerContext = readerContext;
+    this.readerSchema = readerSchema;
+    this.orderingFieldNames = orderingFieldNames;
+  }
+
+  @Override
+  public HoodieLogFile getLogFile() {
+    return logFile;
+  }
+
+  @Override
+  public boolean hasPrev() {
+    return false;
+  }
+
+  @Override
+  public HoodieLogBlock prev() {
+    throw new HoodieNotSupportedException("Reverse reading is not supported 
for native log files");
+  }
+
+  @Override
+  public void close() {
+    // no-op
+  }
+
+  @Override
+  public boolean hasNext() {
+    return !consumed;
+  }
+
+  @Override
+  public HoodieLogBlock next() {
+    if (!hasNext()) {
+      throw new NoSuchElementException("No more blocks in native log file " + 
logFile);
+    }
+    consumed = true;
+    HoodieFileFormat fileFormat = getNativeFileFormat();
+    Map<HeaderMetadataType, String> header = getLogBlockHeader(fileFormat);
+    if (FSUtils.isNativeDeleteLogFile(logFile.getFileName())) {
+      HoodieSchema deleteLogSchema = HoodieSchemas.createDeleteLogSchema(
+          readerContext.getSchemaHandler().getTableSchema(), 
orderingFieldNames);
+      return new HoodieNativeDeleteBlock(storage, logFile, readerContext, 
deleteLogSchema,
+          orderingFieldNames, header, new HashMap<>());
+    }
+    validateDataBlockHeader(logFile, header);
+    return new HoodieNativeDataBlock(storage, logFile, fileFormat, 
Option.ofNullable(readerSchema), header, new HashMap<>());
+  }
+
+  @Override
+  public void remove() {
+    throw new UnsupportedOperationException("Remove not supported for 
HoodieNativeLogFileReader");
+  }
+
+  private Map<HeaderMetadataType, String> getLogBlockHeader(HoodieFileFormat 
fileFormat) {
+    Map<String, String> keyValueMetadata = 
HoodieIOFactory.getIOFactory(storage)
+        .getFileFormatUtils(fileFormat)
+        .readFooter(storage, false, logFile.getPath(), 
NativeLogFooterMetadata.FOOTER_METADATA_KEY);
+    Map<HeaderMetadataType, String> header = 
NativeLogFooterMetadata.fromFooterMetadata(keyValueMetadata);
+    header.putIfAbsent(HeaderMetadataType.INSTANT_TIME, 
logFile.getDeltaCommitTime());
+    return header;
+  }
+
+  static void validateDataBlockHeader(HoodieLogFile logFile, 
Map<HeaderMetadataType, String> header) {
+    if (!header.containsKey(HeaderMetadataType.SCHEMA)) {
+      throw new HoodieIOException("Missing required native log block header 
metadata '"
+          + HeaderMetadataType.SCHEMA.name() + "' for " + logFile
+          + ". Native data logs must store log block metadata in footer key '"
+          + NativeLogFooterMetadata.FOOTER_METADATA_KEY + "'");
+    }
+  }
+
+  private HoodieFileFormat getNativeFileFormat() {
+    String suffix = logFile.getSuffix();
+    try {
+      return HoodieFileFormat.fromFileExtension("." + suffix);
+    } catch (IllegalArgumentException e) {
+      throw new HoodieNotSupportedException(
+          "Unsupported native log file format suffix '" + suffix + "' for " + 
logFile);
+    }
+  }
+}
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/table/log/NativeLogFooterMetadata.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/table/log/NativeLogFooterMetadata.java
new file mode 100644
index 000000000000..9e613a449747
--- /dev/null
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/table/log/NativeLogFooterMetadata.java
@@ -0,0 +1,171 @@
+/*
+ * 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.hudi.common.table.log;
+
+import org.apache.hudi.common.model.HoodieFileFormat;
+import org.apache.hudi.common.schema.HoodieSchema;
+import 
org.apache.hudi.common.table.log.block.HoodieLogBlock.HeaderMetadataType;
+import org.apache.hudi.common.util.JsonUtils;
+import org.apache.hudi.common.util.StringUtils;
+import org.apache.hudi.exception.HoodieIOException;
+import org.apache.hudi.exception.HoodieNotSupportedException;
+import org.apache.hudi.io.storage.HoodieIOFactory;
+import org.apache.hudi.storage.HoodieStorage;
+import org.apache.hudi.storage.StoragePath;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+
+/**
+ * Shared on-disk contract for RFC-103 native log files. The log block header 
(schema, instant time,
+ * etc.) is persisted as a single JSON entry in the native file footer, keyed 
by
+ * {@link #FOOTER_METADATA_KEY}. This is the single source of truth used by 
both the write path
+ * ({@code HoodieNativeLogFormatWriter}) and the read path ({@code 
HoodieNativeLogFileReader}) so the
+ * two cannot drift apart.
+ */
+public class NativeLogFooterMetadata {
+
+  /**
+   * Footer key under which the serialized log block header is stored in a 
native log file.
+   */
+  public static final String FOOTER_METADATA_KEY = "hudi.log.format.metadata";
+
+  private static final TypeReference<LinkedHashMap<String, String>> MAP_TYPE =
+      new TypeReference<LinkedHashMap<String, String>>() {
+      };
+
+  private NativeLogFooterMetadata() {
+  }
+
+  /**
+   * Serializes the log block header into the footer key/value map written to 
the native file.
+   * The {@link HeaderMetadataType#VERSION} entry is injected automatically.
+   */
+  public static Map<String, String> toFooterMetadata(Map<HeaderMetadataType, 
String> header) {
+    Map<String, String> logFormatMetadata = new LinkedHashMap<>();
+    logFormatMetadata.put(HeaderMetadataType.VERSION.name(), 
String.valueOf(HoodieLogFormat.CURRENT_VERSION));
+    header.forEach((key, value) -> {
+      if (value != null) {
+        logFormatMetadata.put(key.name(), value);
+      }
+    });
+    String serialized;
+    try {
+      serialized = 
JsonUtils.getObjectMapper().writeValueAsString(logFormatMetadata);
+    } catch (IOException e) {
+      throw new HoodieIOException("Failed to serialize native log footer 
metadata", e);
+    }
+    Map<String, String> footer = new LinkedHashMap<>();
+    footer.put(FOOTER_METADATA_KEY, serialized);
+    return footer;
+  }
+
+  /**
+   * Parses the log block header back from the native file footer key/value 
map. Unknown header
+   * types are ignored so that files written by newer minor versions remain 
readable.
+   */
+  public static Map<HeaderMetadataType, String> fromFooterMetadata(Map<String, 
String> footerMetadata) {
+    Map<HeaderMetadataType, String> header = new LinkedHashMap<>();
+    String serialized = footerMetadata.get(FOOTER_METADATA_KEY);
+    if (serialized == null) {
+      return header;
+    }
+    Map<String, String> logFormatMetadata;
+    try {
+      logFormatMetadata = JsonUtils.getObjectMapper().readValue(serialized, 
MAP_TYPE);
+    } catch (IOException e) {
+      throw new HoodieIOException("Failed to parse native log footer metadata: 
" + serialized, e);
+    }
+    logFormatMetadata.forEach((name, value) -> {
+      HeaderMetadataType type = headerTypeOrNull(name);
+      if (type != null) {
+        header.put(type, value);
+      }
+    });
+    validateFormatVersion(header);
+    return header;
+  }
+
+  /**
+   * Reads the table schema embedded in a native data log file footer.
+   *
+   * <p>Native data logs persist the synthetic log-block header, including 
{@link HeaderMetadataType#SCHEMA},
+   * in the footer metadata entry keyed by {@link #FOOTER_METADATA_KEY}. This 
method is intended for
+   * schema discovery paths that do not have a {@code HoodieReaderContext} and 
therefore cannot construct
+   * a {@code HoodieNativeLogFileReader}. Native delete logs do not expose a 
generic table schema through
+   * this API because their schema depends on the table schema and configured 
ordering fields; those files
+   * return {@code null}, matching the legacy behavior when no data-block 
schema can be discovered.
+   *
+   * @param storage          storage used to read the native log file footer
+   * @param path             native log file path
+   * @param nativeLogMatcher matcher returned by {@code 
FSUtils.matchNativeLogFile(path.getName())}
+   * @return table schema for native data logs, or {@code null} for native 
non-data logs
+   */
+  public static HoodieSchema readSchemaFromNativeLogFile(
+      HoodieStorage storage, StoragePath path, Matcher nativeLogMatcher) {
+    String nativeLogType = nativeLogMatcher.group(8);
+    if (!"log".equals(nativeLogType)) {
+      return null;
+    }
+
+    HoodieFileFormat fileFormat = HoodieFileFormat.fromFileExtension("." + 
nativeLogMatcher.group(9));
+    Map<String, String> footer = HoodieIOFactory.getIOFactory(storage)
+        .getFileFormatUtils(fileFormat)
+        .readFooter(storage, false, path, FOOTER_METADATA_KEY);
+    Map<HeaderMetadataType, String> header = fromFooterMetadata(footer);
+    String schema = header.get(HeaderMetadataType.SCHEMA);
+    if (StringUtils.isNullOrEmpty(schema)) {
+      throw new HoodieIOException("Missing required native log schema metadata 
'"
+          + HeaderMetadataType.SCHEMA.name() + "' in footer key '"
+          + FOOTER_METADATA_KEY + "' for " + path);
+    }
+    return HoodieSchema.parse(schema);
+  }
+
+  private static void validateFormatVersion(Map<HeaderMetadataType, String> 
header) {
+    String version = header.get(HeaderMetadataType.VERSION);
+    if (version == null) {
+      return;
+    }
+    int parsedVersion;
+    try {
+      parsedVersion = Integer.parseInt(version.trim());
+    } catch (NumberFormatException e) {
+      throw new HoodieIOException("Invalid native log format version in footer 
metadata: " + version);
+    }
+    if (parsedVersion > HoodieLogFormat.CURRENT_VERSION) {
+      throw new HoodieNotSupportedException("Native log format version " + 
parsedVersion
+          + " is newer than the supported version " + 
HoodieLogFormat.CURRENT_VERSION
+          + ". Please upgrade Hudi to read this log file.");
+    }
+  }
+
+  private static HeaderMetadataType headerTypeOrNull(String name) {
+    try {
+      return HeaderMetadataType.valueOf(name);
+    } catch (IllegalArgumentException e) {
+      return null;
+    }
+  }
+}
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/table/log/block/HoodieDataBlock.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/table/log/block/HoodieDataBlock.java
index dff48985f646..64e1ac881e22 100644
--- 
a/hudi-common/src/main/java/org/apache/hudi/common/table/log/block/HoodieDataBlock.java
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/table/log/block/HoodieDataBlock.java
@@ -469,7 +469,7 @@ public abstract class HoodieDataBlock extends 
HoodieLogBlock {
    *
    * @param <T> The type of engine-specific record representation.
    */
-  private static class FilteringEngineRecordIterator<T> implements 
ClosableIterator<T> {
+  protected static class FilteringEngineRecordIterator<T> implements 
ClosableIterator<T> {
     private final ClosableIterator<T> nested; // nested iterator
 
     private final Set<String> keys; // the filtering keys
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/table/log/block/HoodieDeleteBlock.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/table/log/block/HoodieDeleteBlock.java
index 0d69dab14258..cb2923f1f2fa 100644
--- 
a/hudi-common/src/main/java/org/apache/hudi/common/table/log/block/HoodieDeleteBlock.java
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/table/log/block/HoodieDeleteBlock.java
@@ -21,9 +21,12 @@ package org.apache.hudi.common.table.log.block;
 import org.apache.hudi.avro.HoodieAvroUtils;
 import org.apache.hudi.avro.model.HoodieDeleteRecord;
 import org.apache.hudi.avro.model.HoodieDeleteRecordList;
+import org.apache.hudi.common.engine.RecordContext;
 import org.apache.hudi.common.fs.SizeAwareDataInputStream;
 import org.apache.hudi.common.model.DeleteRecord;
 import org.apache.hudi.common.model.HoodieKey;
+import org.apache.hudi.common.table.read.BufferedRecord;
+import org.apache.hudi.common.table.read.BufferedRecords;
 import org.apache.hudi.common.util.Option;
 import org.apache.hudi.common.util.SerializationUtils;
 import org.apache.hudi.common.util.collection.Pair;
@@ -131,6 +134,15 @@ public class HoodieDeleteBlock extends HoodieLogBlock {
     }
   }
 
+  /**
+   * Returns delete records in the same representation used by the file-group 
record buffer.
+   */
+  public <T> List<BufferedRecord<T>> getRecordsToDelete(RecordContext<T> 
recordContext) {
+    return Arrays.stream(getRecordsToDelete())
+        .map(deleteRecord -> BufferedRecords.fromDeleteRecord(deleteRecord, 
recordContext))
+        .collect(Collectors.toList());
+  }
+
   private byte[] serializeV2() throws IOException {
     // Serialization for log block version 2
     return SerializationUtils.serialize(getRecordsToDelete());
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/table/log/block/HoodieNativeDataBlock.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/table/log/block/HoodieNativeDataBlock.java
new file mode 100644
index 000000000000..805e84e89198
--- /dev/null
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/table/log/block/HoodieNativeDataBlock.java
@@ -0,0 +1,133 @@
+/*
+ * 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.hudi.common.table.log.block;
+
+import org.apache.hudi.common.engine.HoodieReaderContext;
+import org.apache.hudi.common.fs.FSUtils;
+import org.apache.hudi.common.model.HoodieFileFormat;
+import org.apache.hudi.common.model.HoodieLogFile;
+import org.apache.hudi.common.model.HoodieRecord;
+import org.apache.hudi.common.schema.HoodieSchema;
+import org.apache.hudi.common.util.Option;
+import org.apache.hudi.common.util.collection.ClosableIterator;
+import org.apache.hudi.io.SeekableDataInputStream;
+import org.apache.hudi.io.storage.HoodieIOFactory;
+import org.apache.hudi.storage.HoodieStorage;
+import org.apache.hudi.storage.StoragePath;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import static 
org.apache.hudi.common.util.ConfigUtils.DEFAULT_HUDI_CONFIG_FOR_READER;
+
+/**
+ * Data block backed by a native log file.
+ */
+public class HoodieNativeDataBlock extends HoodieDataBlock {
+
+  private final HoodieStorage storage;
+  private final HoodieLogFile logFile;
+  private final HoodieFileFormat fileFormat;
+
+  public HoodieNativeDataBlock(HoodieStorage storage,
+                               HoodieLogFile logFile,
+                               HoodieFileFormat fileFormat,
+                               Option<HoodieSchema> readerSchema,
+                               Map<HeaderMetadataType, String> header,
+                               Map<FooterMetadataType, String> footer) {
+    super(Option.empty(), null, true, getContentLocation(storage, logFile), 
readerSchema,
+        header, footer, HoodieRecord.RECORD_KEY_METADATA_FIELD, false);
+    this.storage = storage;
+    this.logFile = logFile;
+    this.fileFormat = fileFormat;
+  }
+
+  @Override
+  public HoodieLogBlockType getBlockType() {
+    // This is a synthetic reader-side block for native log files. Returning 
PARQUET_DATA_BLOCK
+    // keeps the existing data-block processing/statistics path; native file 
decoding is driven
+    // by fileFormat and readRecordsFromBlockPayload, not by this block type.
+    return HoodieLogBlockType.PARQUET_DATA_BLOCK;
+  }
+
+  @Override
+  protected <T> ClosableIterator<HoodieRecord<T>> 
readRecordsFromBlockPayload(HoodieRecord.HoodieRecordType type) throws 
IOException {
+    StoragePath path = logFile.getPath();
+    return HoodieIOFactory.getIOFactory(storage)
+        .getReaderFactory(type)
+        .getFileReader(DEFAULT_HUDI_CONFIG_FOR_READER, path, fileFormat, 
Option.empty())
+        .getRecordIterator(getSchemaFromHeader(), readerSchema);
+  }
+
+  @Override
+  protected <T> ClosableIterator<HoodieRecord<T>> 
readRecordsFromBlockPayload(HoodieRecord.HoodieRecordType type, int 
ignoredBufferSize) throws IOException {
+    return readRecordsFromBlockPayload(type);
+  }
+
+  @Override
+  protected <T> ClosableIterator<T> 
readRecordsFromBlockPayload(HoodieReaderContext<T> readerContext) throws 
IOException {
+    return readerContext.getFileRecordIterator(
+        logFile.getPath(), 0, FSUtils.getFileSize(storage, logFile),
+        getSchemaFromHeader(),
+        readerSchema,
+        storage);
+  }
+
+  @Override
+  protected <T> ClosableIterator<T> lookupEngineRecords(HoodieReaderContext<T> 
readerContext, List<String> keys, boolean fullKey) throws IOException {
+    return FilteringEngineRecordIterator.getInstance(
+        readRecordsFromBlockPayload(readerContext),
+        new HashSet<>(keys),
+        fullKey,
+        record -> 
Option.ofNullable(readerContext.getRecordContext().getRecordKey(record, 
readerSchema)));
+  }
+
+  @Override
+  protected ByteArrayOutputStream serializeRecords(List<HoodieRecord> records, 
HoodieStorage storage) {
+    throw new UnsupportedOperationException("Native log data blocks are 
read-only");
+  }
+
+  @Override
+  protected <T> ClosableIterator<HoodieRecord<T>> deserializeRecords(byte[] 
content, HoodieRecord.HoodieRecordType type) {
+    throw new UnsupportedOperationException("Native log data blocks read 
records directly from native files");
+  }
+
+  @Override
+  protected <T> ClosableIterator<HoodieRecord<T>> deserializeRecords(
+      SeekableDataInputStream inputStream,
+      HoodieLogBlockContentLocation contentLocation,
+      HoodieRecord.HoodieRecordType type,
+      int bufferSize) {
+    throw new UnsupportedOperationException("Native log data blocks do not use 
inline log block content");
+  }
+
+  @Override
+  protected <T> ClosableIterator<T> deserializeRecords(HoodieReaderContext<T> 
readerContext, byte[] content) {
+    throw new UnsupportedOperationException("Native log data blocks read 
records directly from native files");
+  }
+
+  private static Option<HoodieLogBlockContentLocation> 
getContentLocation(HoodieStorage storage, HoodieLogFile logFile) {
+    long fileSize = FSUtils.getFileSize(storage, logFile);
+    return Option.of(new HoodieLogBlockContentLocation(storage, logFile, 0, 
fileSize, fileSize));
+  }
+}
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/table/log/block/HoodieNativeDeleteBlock.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/table/log/block/HoodieNativeDeleteBlock.java
new file mode 100644
index 000000000000..37064e6ce7bb
--- /dev/null
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/table/log/block/HoodieNativeDeleteBlock.java
@@ -0,0 +1,106 @@
+/*
+ * 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.hudi.common.table.log.block;
+
+import org.apache.hudi.common.engine.HoodieReaderContext;
+import org.apache.hudi.common.engine.RecordContext;
+import org.apache.hudi.common.fs.FSUtils;
+import org.apache.hudi.common.model.DeleteRecord;
+import org.apache.hudi.common.model.HoodieLogFile;
+import org.apache.hudi.common.model.HoodieRecord;
+import org.apache.hudi.common.schema.HoodieSchema;
+import org.apache.hudi.common.table.read.BufferedRecord;
+import org.apache.hudi.common.table.read.BufferedRecords;
+import org.apache.hudi.common.util.Option;
+import org.apache.hudi.common.util.collection.ClosableIterator;
+import org.apache.hudi.exception.HoodieIOException;
+import org.apache.hudi.exception.HoodieNotSupportedException;
+import org.apache.hudi.storage.HoodieStorage;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Delete block backed by a native delete log file.
+ */
+public class HoodieNativeDeleteBlock extends HoodieDeleteBlock {
+
+  private final HoodieStorage storage;
+  private final HoodieLogFile logFile;
+  private final HoodieReaderContext<?> readerContext;
+  private final HoodieSchema deleteLogSchema;
+  private final List<String> orderingFieldNames;
+  private List<BufferedRecord<?>> bufferedRecordsToDelete;
+
+  public HoodieNativeDeleteBlock(HoodieStorage storage,
+                                 HoodieLogFile logFile,
+                                 HoodieReaderContext<?> readerContext,
+                                 HoodieSchema deleteLogSchema,
+                                 List<String> orderingFieldNames,
+                                 Map<HeaderMetadataType, String> header,
+                                 Map<FooterMetadataType, String> footer) {
+    super(Option.empty(), null, true, getContentLocation(storage, logFile), 
header, footer);
+    this.storage = storage;
+    this.logFile = logFile;
+    this.readerContext = readerContext;
+    this.deleteLogSchema = deleteLogSchema;
+    this.orderingFieldNames = orderingFieldNames;
+  }
+
+  @Override
+  public DeleteRecord[] getRecordsToDelete() {
+    throw new HoodieNotSupportedException("Native delete log files do not 
support the legacy DeleteRecord[] API. "
+        + "Use getRecordsToDelete(RecordContext) instead. Log file: " + 
logFile);
+  }
+
+  @Override
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  public <T> List<BufferedRecord<T>> getRecordsToDelete(RecordContext<T> 
recordContext) {
+    if (bufferedRecordsToDelete == null) {
+      bufferedRecordsToDelete = (List) 
readBufferedRecordsToDelete(recordContext);
+    }
+    return (List) bufferedRecordsToDelete;
+  }
+
+  @SuppressWarnings("unchecked")
+  private <T> List<BufferedRecord<T>> 
readBufferedRecordsToDelete(RecordContext<T> recordContext) {
+    HoodieReaderContext<T> typedReaderContext = (HoodieReaderContext<T>) 
readerContext;
+    List<BufferedRecord<T>> deleteRecords = new ArrayList<>();
+    try (ClosableIterator<T> recordIterator = 
typedReaderContext.getFileRecordIterator(
+        logFile.getPath(), 0, FSUtils.getFileSize(storage, logFile), 
deleteLogSchema, deleteLogSchema, storage)) {
+      while (recordIterator.hasNext()) {
+        T record = recordIterator.next();
+        Object recordKey = recordContext.getValue(record, deleteLogSchema, 
HoodieRecord.RECORD_KEY_METADATA_FIELD);
+        Comparable orderingValue = recordContext.getOrderingValue(record, 
deleteLogSchema, orderingFieldNames);
+        deleteRecords.add(BufferedRecords.createDelete(recordKey.toString(), 
orderingValue));
+      }
+    } catch (IOException e) {
+      throw new HoodieIOException("Failed to read native delete log file " + 
logFile, e);
+    }
+    return deleteRecords;
+  }
+
+  private static Option<HoodieLogBlockContentLocation> 
getContentLocation(HoodieStorage storage, HoodieLogFile logFile) {
+    long fileSize = FSUtils.getFileSize(storage, logFile);
+    return Option.of(new HoodieLogBlockContentLocation(storage, logFile, 0, 
fileSize, fileSize));
+  }
+}
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/table/read/BufferedRecordMerger.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/table/read/BufferedRecordMerger.java
index e8c1d103575e..9c6d8f1ca5f8 100644
--- 
a/hudi-common/src/main/java/org/apache/hudi/common/table/read/BufferedRecordMerger.java
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/table/read/BufferedRecordMerger.java
@@ -50,11 +50,15 @@ public interface BufferedRecordMerger<T> extends 
Serializable {
   /**
    * Merges incoming delete record from log file with existing record in 
record buffer.
    *
+   * @deprecated Delete records should be converted to {@link BufferedRecord}s 
and merged through
+   *             {@link #deltaMerge(BufferedRecord, BufferedRecord)}.
+   *
    * @param deleteRecord   Incoming delete record from log file
    * @param existingRecord Existing record in record buffer
    *
    * @return The merged record.
    */
+  @Deprecated
   Option<DeleteRecord> deltaMerge(DeleteRecord deleteRecord, BufferedRecord<T> 
existingRecord);
 
   /**
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/table/read/BufferedRecordMergerFactory.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/table/read/BufferedRecordMergerFactory.java
index 06e903dd1fa4..4484abaefee9 100644
--- 
a/hudi-common/src/main/java/org/apache/hudi/common/table/read/BufferedRecordMergerFactory.java
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/table/read/BufferedRecordMergerFactory.java
@@ -110,6 +110,7 @@ public class BufferedRecordMergerFactory {
     }
 
     @Override
+    @Deprecated
     public Option<DeleteRecord> deltaMerge(DeleteRecord deleteRecord, 
BufferedRecord<T> existingRecord) {
       return Option.of(deleteRecord);
     }
@@ -139,6 +140,9 @@ public class BufferedRecordMergerFactory {
     @Override
     public Option<BufferedRecord<T>> deltaMerge(BufferedRecord<T> newRecord,
                                                 BufferedRecord<T> 
existingRecord) {
+      if (newRecord.isDelete()) {
+        return Option.of(newRecord);
+      }
       if (existingRecord != null) {
         HoodieSchema newSchema = 
recordContext.getSchemaFromBufferRecord(newRecord);
         newRecord = partialUpdateHandler.partialMerge(
@@ -154,6 +158,9 @@ public class BufferedRecordMergerFactory {
     @Override
     public BufferedRecord<T> finalMerge(BufferedRecord<T> olderRecord,
                                      BufferedRecord<T> newerRecord) {
+      if (newerRecord.isDelete()) {
+        return newerRecord;
+      }
       HoodieSchema newSchema = 
recordContext.getSchemaFromBufferRecord(newerRecord);
       newerRecord = partialUpdateHandler.partialMerge(
           newerRecord,
@@ -185,6 +192,7 @@ public class BufferedRecordMergerFactory {
     }
 
     @Override
+    @Deprecated
     public Option<DeleteRecord> deltaMerge(DeleteRecord deleteRecord, 
BufferedRecord<T> existingRecord) {
       return deltaMergeDeleteRecord(deleteRecord, existingRecord, 
recordContext);
     }
@@ -216,6 +224,9 @@ public class BufferedRecordMergerFactory {
 
     @Override
     public Option<BufferedRecord<T>> deltaMerge(BufferedRecord<T> newRecord, 
BufferedRecord<T> existingRecord) {
+      if (newRecord.isDelete()) {
+        return super.deltaMerge(newRecord, existingRecord);
+      }
       if (existingRecord == null) {
         return Option.of(newRecord);
       } else if (shouldKeepNewerRecord(existingRecord, newRecord)) {
@@ -242,6 +253,9 @@ public class BufferedRecordMergerFactory {
 
     @Override
     public BufferedRecord<T> finalMerge(BufferedRecord<T> olderRecord, 
BufferedRecord<T> newerRecord) {
+      if (newerRecord.isDelete()) {
+        return super.finalMerge(olderRecord, newerRecord);
+      }
       if (newerRecord.isCommitTimeOrderingDelete()) {
         return newerRecord;
       }
@@ -297,6 +311,9 @@ public class BufferedRecordMergerFactory {
 
     @Override
     public Option<BufferedRecord<T>> deltaMerge(BufferedRecord<T> newRecord, 
BufferedRecord<T> existingRecord) throws IOException {
+      if (newRecord.isDelete()) {
+        return deleteRecordMerger.deltaMerge(newRecord, existingRecord);
+      }
       if (existingRecord == null) {
         return Option.of(newRecord);
       }
@@ -318,12 +335,16 @@ public class BufferedRecordMergerFactory {
     }
 
     @Override
+    @Deprecated
     public Option<DeleteRecord> deltaMerge(DeleteRecord deleteRecord, 
BufferedRecord<T> existingRecord) {
       return this.deleteRecordMerger.deltaMerge(deleteRecord, existingRecord);
     }
 
     @Override
     public BufferedRecord<T> finalMerge(BufferedRecord<T> olderRecord, 
BufferedRecord<T> newerRecord) throws IOException {
+      if (newerRecord.isDelete()) {
+        return deleteRecordMerger.finalMerge(olderRecord, newerRecord);
+      }
       // TODO(HUDI-7843): decouple the merging logic from the merger
       //  and use the record merge mode to control how to merge partial updates
       return recordMerger.get().partialMerge(olderRecord, newerRecord, 
readerSchema, recordContext, props);
@@ -442,6 +463,7 @@ public class BufferedRecordMergerFactory {
     public abstract Option<BufferedRecord<T>> 
deltaMergeRecords(BufferedRecord<T> newRecord, BufferedRecord<T> 
existingRecord) throws IOException;
 
     @Override
+    @Deprecated
     public Option<DeleteRecord> deltaMerge(DeleteRecord deleteRecord, 
BufferedRecord<T> existingRecord) {
       BufferedRecord<T> deleteBufferedRecord = 
BufferedRecords.fromDeleteRecord(deleteRecord, recordContext);
       try {
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/HoodieFileGroupRecordBuffer.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/HoodieFileGroupRecordBuffer.java
index f3ebd1813959..24594123e324 100644
--- 
a/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/HoodieFileGroupRecordBuffer.java
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/HoodieFileGroupRecordBuffer.java
@@ -19,7 +19,6 @@
 
 package org.apache.hudi.common.table.read.buffer;
 
-import org.apache.hudi.common.model.DeleteRecord;
 import org.apache.hudi.common.table.log.KeySpec;
 import org.apache.hudi.common.table.log.block.HoodieDataBlock;
 import org.apache.hudi.common.table.log.block.HoodieDeleteBlock;
@@ -72,11 +71,6 @@ public interface HoodieFileGroupRecordBuffer<T> {
    */
   void processDeleteBlock(HoodieDeleteBlock deleteBlock) throws IOException;
 
-  /**
-   * Process next delete record.
-   */
-  void processNextDeletedRecord(DeleteRecord record, Serializable index);
-
   /**
    * Check if a record exists in the buffered records.
    */
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/KeyBasedFileGroupRecordBuffer.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/KeyBasedFileGroupRecordBuffer.java
index f052f5cee844..afbdfd0eed5a 100644
--- 
a/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/KeyBasedFileGroupRecordBuffer.java
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/KeyBasedFileGroupRecordBuffer.java
@@ -23,7 +23,6 @@ import org.apache.hudi.common.config.RecordMergeMode;
 import org.apache.hudi.common.config.TypedProperties;
 import org.apache.hudi.common.engine.HoodieReaderContext;
 import org.apache.hudi.common.engine.RecordContext;
-import org.apache.hudi.common.model.DeleteRecord;
 import org.apache.hudi.common.schema.HoodieSchema;
 import org.apache.hudi.common.schema.HoodieSchemaCache;
 import org.apache.hudi.common.table.HoodieTableMetaClient;
@@ -42,8 +41,6 @@ import org.apache.hudi.common.util.collection.Pair;
 
 import java.io.IOException;
 import java.io.Serializable;
-import java.util.Arrays;
-import java.util.Iterator;
 import java.util.List;
 
 /**
@@ -111,22 +108,11 @@ public class KeyBasedFileGroupRecordBuffer<T> extends 
FileGroupRecordBuffer<T> {
 
   @Override
   public void processDeleteBlock(HoodieDeleteBlock deleteBlock) throws 
IOException {
-    Iterator<DeleteRecord> it = 
Arrays.stream(deleteBlock.getRecordsToDelete()).iterator();
-    while (it.hasNext()) {
-      DeleteRecord record = it.next();
-      processNextDeletedRecord(record, record.getRecordKey());
+    for (BufferedRecord<T> record : 
deleteBlock.getRecordsToDelete(readerContext.getRecordContext())) {
+      processNextDataRecord(record, record.getRecordKey());
     }
   }
 
-  @Override
-  public void processNextDeletedRecord(DeleteRecord deleteRecord, Serializable 
recordIdentifier) {
-    BufferedRecord<T> existingRecord = records.get(recordIdentifier);
-    totalLogRecords++;
-    Option<DeleteRecord> recordOpt = 
bufferedRecordMerger.deltaMerge(deleteRecord, existingRecord);
-    recordOpt.ifPresent(deleteRec ->
-        records.put(recordIdentifier, 
BufferedRecords.fromDeleteRecord(deleteRec, readerContext.getRecordContext())));
-  }
-
   @Override
   public boolean containsLogRecord(String recordKey) {
     return records.containsKey(recordKey);
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/PositionBasedFileGroupRecordBuffer.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/PositionBasedFileGroupRecordBuffer.java
index 5eaa54e033c8..62a851a4b9d3 100644
--- 
a/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/PositionBasedFileGroupRecordBuffer.java
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/PositionBasedFileGroupRecordBuffer.java
@@ -22,7 +22,6 @@ package org.apache.hudi.common.table.read.buffer;
 import org.apache.hudi.common.config.RecordMergeMode;
 import org.apache.hudi.common.config.TypedProperties;
 import org.apache.hudi.common.engine.HoodieReaderContext;
-import org.apache.hudi.common.model.DeleteRecord;
 import org.apache.hudi.common.schema.HoodieSchema;
 import org.apache.hudi.common.schema.HoodieSchemaCache;
 import org.apache.hudi.common.table.HoodieTableMetaClient;
@@ -47,7 +46,6 @@ import org.roaringbitmap.longlong.Roaring64NavigableMap;
 import java.io.IOException;
 import java.io.Serializable;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -189,7 +187,7 @@ public class PositionBasedFileGroupRecordBuffer<T> extends 
KeyBasedFileGroupReco
     switch (recordMergeMode) {
       case COMMIT_TIME_ORDERING:
         int commitTimeBasedRecordIndex = 0;
-        DeleteRecord[] deleteRecords = deleteBlock.getRecordsToDelete();
+        List<BufferedRecord<T>> deleteRecords = 
deleteBlock.getRecordsToDelete(readerContext.getRecordContext());
         for (Long recordPosition : recordPositions) {
           // IMPORTANT:
           // use #put for log files with regular order(see 
HoodieLogFile.LOG_FILE_COMPARATOR);
@@ -200,20 +198,16 @@ public class PositionBasedFileGroupRecordBuffer<T> 
extends KeyBasedFileGroupReco
           // because under hybrid strategy in #doHasNextFallbackBaseRecord, if 
the record keys are not set up,
           // this delete-vector could be kept in the records cache(see the 
check in #fallbackToKeyBasedBuffer),
           // and these keys would be deleted no matter whether there are 
following-up inserts/updates.
-          DeleteRecord deleteRecord = 
deleteRecords[commitTimeBasedRecordIndex++];
-          BufferedRecord<T> record = 
BufferedRecords.fromDeleteRecord(deleteRecord, 
readerContext.getRecordContext());
-          records.put(recordPosition, record);
+          records.put(recordPosition, 
deleteRecords.get(commitTimeBasedRecordIndex++));
         }
         return;
       case EVENT_TIME_ORDERING:
       case CUSTOM:
       default:
         int recordIndex = 0;
-        Iterator<DeleteRecord> it = 
Arrays.stream(deleteBlock.getRecordsToDelete()).iterator();
-        while (it.hasNext()) {
-          DeleteRecord record = it.next();
+        for (BufferedRecord<T> record : 
deleteBlock.getRecordsToDelete(readerContext.getRecordContext())) {
           long recordPosition = recordPositions.get(recordIndex++);
-          processNextDeletedRecord(record, recordPosition);
+          processNextDataRecord(record, recordPosition);
         }
     }
   }
@@ -314,4 +308,4 @@ public class PositionBasedFileGroupRecordBuffer<T> extends 
KeyBasedFileGroupReco
 
     return blockPositions;
   }
-}
\ No newline at end of file
+}
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/ReusableKeyBasedRecordBuffer.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/ReusableKeyBasedRecordBuffer.java
index bce8c7f9e74f..80867641c1a4 100644
--- 
a/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/ReusableKeyBasedRecordBuffer.java
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/ReusableKeyBasedRecordBuffer.java
@@ -21,7 +21,6 @@ package org.apache.hudi.common.table.read.buffer;
 import org.apache.hudi.common.config.RecordMergeMode;
 import org.apache.hudi.common.config.TypedProperties;
 import org.apache.hudi.common.engine.HoodieReaderContext;
-import org.apache.hudi.common.model.DeleteRecord;
 import org.apache.hudi.common.table.HoodieTableMetaClient;
 import org.apache.hudi.common.table.PartialUpdateMode;
 import org.apache.hudi.common.table.log.KeySpec;
@@ -123,11 +122,6 @@ public class ReusableKeyBasedRecordBuffer<T> extends 
FileGroupRecordBuffer<T> {
 
   }
 
-  @Override
-  public void processNextDeletedRecord(DeleteRecord record, Serializable 
index) {
-    throw new HoodieNotSupportedException("Reusable record buffer does not 
process the delete records from the logs");
-  }
-
   @Override
   public boolean containsLogRecord(String recordKey) {
     return validKeys.contains(recordKey) && 
existingRecords.containsKey(recordKey);
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/UnmergedFileGroupRecordBuffer.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/UnmergedFileGroupRecordBuffer.java
index b1c2a12a24ee..66a437f11ead 100644
--- 
a/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/UnmergedFileGroupRecordBuffer.java
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/table/read/buffer/UnmergedFileGroupRecordBuffer.java
@@ -22,7 +22,6 @@ package org.apache.hudi.common.table.read.buffer;
 import org.apache.hudi.common.config.RecordMergeMode;
 import org.apache.hudi.common.config.TypedProperties;
 import org.apache.hudi.common.engine.HoodieReaderContext;
-import org.apache.hudi.common.model.DeleteRecord;
 import org.apache.hudi.common.schema.HoodieSchema;
 import org.apache.hudi.common.table.HoodieTableMetaClient;
 import org.apache.hudi.common.table.PartialUpdateMode;
@@ -114,11 +113,6 @@ class UnmergedFileGroupRecordBuffer<T> extends 
FileGroupRecordBuffer<T> {
     // no-op
   }
 
-  @Override
-  public void processNextDeletedRecord(DeleteRecord deleteRecord, Serializable 
index) {
-    // no-op
-  }
-
   @Override
   public boolean containsLogRecord(String recordKey) {
     throw new UnsupportedOperationException("Not supported for " + 
this.getClass().getSimpleName());
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/table/view/AbstractTableFileSystemView.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/table/view/AbstractTableFileSystemView.java
index 0c68f9ee4215..75118b90bb2c 100644
--- 
a/hudi-common/src/main/java/org/apache/hudi/common/table/view/AbstractTableFileSystemView.java
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/table/view/AbstractTableFileSystemView.java
@@ -527,11 +527,7 @@ public abstract class AbstractTableFileSystemView 
implements SyncableFileSystemV
    * @param pathInfoList List of StoragePathInfo
    */
   private Stream<HoodieLogFile> 
convertFileStatusesToLogFiles(List<StoragePathInfo> pathInfoList) {
-    String logFileExtension = 
metaClient.getTableConfig().getLogFileFormat().getFileExtension();
-    Predicate<StoragePathInfo> rtFilePredicate = pathInfo -> {
-      String fileName = pathInfo.getPath().getName();
-      return FSUtils.isLogFile(pathInfo.getPath()) && 
fileName.contains(logFileExtension);
-    };
+    Predicate<StoragePathInfo> rtFilePredicate = pathInfo -> 
FSUtils.isLogFile(pathInfo.getPath());
     return 
pathInfoList.stream().filter(rtFilePredicate).map(HoodieLogFile::new);
   }
 
diff --git 
a/hudi-common/src/test/java/org/apache/hudi/common/table/log/TestHoodieNativeLogFileReader.java
 
b/hudi-common/src/test/java/org/apache/hudi/common/table/log/TestHoodieNativeLogFileReader.java
new file mode 100644
index 000000000000..673739d2b9c5
--- /dev/null
+++ 
b/hudi-common/src/test/java/org/apache/hudi/common/table/log/TestHoodieNativeLogFileReader.java
@@ -0,0 +1,51 @@
+/*
+ * 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.hudi.common.table.log;
+
+import org.apache.hudi.common.model.HoodieLogFile;
+import 
org.apache.hudi.common.table.log.block.HoodieLogBlock.HeaderMetadataType;
+import org.apache.hudi.exception.HoodieIOException;
+import org.apache.hudi.storage.StoragePath;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class TestHoodieNativeLogFileReader {
+
+  @Test
+  void testValidateDataBlockHeaderRequiresSchema() {
+    HoodieLogFile logFile = new HoodieLogFile(new StoragePath(
+        "/tmp/file-id_1-0-1_001_1.log.parquet"));
+    Map<HeaderMetadataType, String> header = new HashMap<>();
+    header.put(HeaderMetadataType.INSTANT_TIME, "001");
+
+    HoodieIOException exception = assertThrows(HoodieIOException.class,
+        () -> HoodieNativeLogFileReader.validateDataBlockHeader(logFile, 
header));
+
+    
assertTrue(exception.getMessage().contains(HeaderMetadataType.SCHEMA.name()));
+    
assertTrue(exception.getMessage().contains(NativeLogFooterMetadata.FOOTER_METADATA_KEY));
+    assertTrue(exception.getMessage().contains(logFile.toString()));
+  }
+}
diff --git 
a/hudi-common/src/test/java/org/apache/hudi/common/table/log/TestNativeLogFooterMetadata.java
 
b/hudi-common/src/test/java/org/apache/hudi/common/table/log/TestNativeLogFooterMetadata.java
new file mode 100644
index 000000000000..a7527a0599da
--- /dev/null
+++ 
b/hudi-common/src/test/java/org/apache/hudi/common/table/log/TestNativeLogFooterMetadata.java
@@ -0,0 +1,94 @@
+/*
+ * 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.hudi.common.table.log;
+
+import 
org.apache.hudi.common.table.log.block.HoodieLogBlock.HeaderMetadataType;
+import org.apache.hudi.exception.HoodieNotSupportedException;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class TestNativeLogFooterMetadata {
+
+  @Test
+  void testWriteThenReadRoundTrip() {
+    Map<HeaderMetadataType, String> header = new HashMap<>();
+    header.put(HeaderMetadataType.INSTANT_TIME, "001");
+    header.put(HeaderMetadataType.SCHEMA, 
"{\"type\":\"record\",\"name\":\"test\",\"fields\":[]}");
+    header.put(HeaderMetadataType.IS_PARTIAL, "true");
+
+    // Footer produced by the write path must be readable by the read path.
+    Map<String, String> footer = 
NativeLogFooterMetadata.toFooterMetadata(header);
+    
assertTrue(footer.containsKey(NativeLogFooterMetadata.FOOTER_METADATA_KEY));
+
+    Map<HeaderMetadataType, String> parsed = 
NativeLogFooterMetadata.fromFooterMetadata(footer);
+    assertEquals("001", parsed.get(HeaderMetadataType.INSTANT_TIME));
+    assertEquals("{\"type\":\"record\",\"name\":\"test\",\"fields\":[]}", 
parsed.get(HeaderMetadataType.SCHEMA));
+    assertEquals("true", parsed.get(HeaderMetadataType.IS_PARTIAL));
+    // VERSION is injected on write and surfaced on read.
+    assertEquals(String.valueOf(HoodieLogFormat.CURRENT_VERSION),
+        parsed.get(HeaderMetadataType.VERSION));
+  }
+
+  @Test
+  void testNullHeaderValuesAreDropped() {
+    Map<HeaderMetadataType, String> header = new HashMap<>();
+    header.put(HeaderMetadataType.INSTANT_TIME, "001");
+    header.put(HeaderMetadataType.SCHEMA, null);
+
+    Map<HeaderMetadataType, String> parsed =
+        
NativeLogFooterMetadata.fromFooterMetadata(NativeLogFooterMetadata.toFooterMetadata(header));
+    assertEquals("001", parsed.get(HeaderMetadataType.INSTANT_TIME));
+    assertFalse(parsed.containsKey(HeaderMetadataType.SCHEMA));
+  }
+
+  @Test
+  void testMissingFooterKeyReturnsEmptyHeader() {
+    assertTrue(NativeLogFooterMetadata.fromFooterMetadata(new 
HashMap<>()).isEmpty());
+  }
+
+  @Test
+  void testUnknownHeaderTypesAreIgnored() {
+    Map<String, String> footer = new HashMap<>();
+    footer.put(NativeLogFooterMetadata.FOOTER_METADATA_KEY,
+        
"{\"VERSION\":\"2\",\"INSTANT_TIME\":\"001\",\"SOME_FUTURE_KEY\":\"v\"}");
+
+    Map<HeaderMetadataType, String> parsed = 
NativeLogFooterMetadata.fromFooterMetadata(footer);
+    assertEquals("001", parsed.get(HeaderMetadataType.INSTANT_TIME));
+    assertEquals(2, parsed.size());
+  }
+
+  @Test
+  void testNewerFormatVersionIsRejected() {
+    Map<String, String> footer = new HashMap<>();
+    footer.put(NativeLogFooterMetadata.FOOTER_METADATA_KEY,
+        "{\"VERSION\":\"" + (HoodieLogFormat.CURRENT_VERSION + 1) + 
"\",\"INSTANT_TIME\":\"001\"}");
+
+    assertThrows(HoodieNotSupportedException.class,
+        () -> NativeLogFooterMetadata.fromFooterMetadata(footer));
+  }
+}
diff --git 
a/hudi-common/src/test/java/org/apache/hudi/common/table/read/buffer/BaseTestFileGroupRecordBuffer.java
 
b/hudi-common/src/test/java/org/apache/hudi/common/table/read/buffer/BaseTestFileGroupRecordBuffer.java
index b2869df3310d..0f29ab2c30d4 100644
--- 
a/hudi-common/src/test/java/org/apache/hudi/common/table/read/buffer/BaseTestFileGroupRecordBuffer.java
+++ 
b/hudi-common/src/test/java/org/apache/hudi/common/table/read/buffer/BaseTestFileGroupRecordBuffer.java
@@ -24,6 +24,7 @@ import org.apache.hudi.common.config.TypedProperties;
 import org.apache.hudi.common.engine.HoodieReaderContext;
 import org.apache.hudi.common.engine.RecordContext;
 import org.apache.hudi.common.model.BaseAvroPayload;
+import org.apache.hudi.common.model.DeleteRecord;
 import org.apache.hudi.common.model.HoodieAvroIndexedRecord;
 import org.apache.hudi.common.model.HoodieEmptyRecord;
 import org.apache.hudi.common.model.HoodieKey;
@@ -37,6 +38,7 @@ import org.apache.hudi.common.schema.HoodieSchemaField;
 import org.apache.hudi.common.schema.HoodieSchemaType;
 import org.apache.hudi.common.table.HoodieTableConfig;
 import org.apache.hudi.common.table.HoodieTableMetaClient;
+import org.apache.hudi.common.table.log.block.HoodieDeleteBlock;
 import org.apache.hudi.common.table.read.BufferedRecord;
 import org.apache.hudi.common.table.read.BufferedRecords;
 import org.apache.hudi.common.table.read.DeleteContext;
@@ -67,6 +69,7 @@ import java.util.stream.Stream;
 
 import static 
org.apache.hudi.common.model.DefaultHoodieRecordPayload.DELETE_KEY;
 import static 
org.apache.hudi.common.model.DefaultHoodieRecordPayload.DELETE_MARKER;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -87,6 +90,16 @@ public class BaseTestFileGroupRecordBuffer {
     return record;
   }
 
+  protected static void mockDeleteRecords(HoodieDeleteBlock deleteBlock, 
DeleteRecord... deleteRecords) {
+    when(deleteBlock.getRecordsToDelete()).thenReturn(deleteRecords);
+    when(deleteBlock.getRecordsToDelete(any())).thenAnswer(invocation -> {
+      RecordContext recordContext = invocation.getArgument(0);
+      return Arrays.stream(deleteRecords)
+          .map(deleteRecord -> BufferedRecords.fromDeleteRecord(deleteRecord, 
recordContext))
+          .collect(Collectors.toList());
+    });
+  }
+
   protected static List<HoodieRecord> 
convertToHoodieRecordsList(List<IndexedRecord> indexedRecords) {
     return indexedRecords.stream().map(rec -> new HoodieAvroIndexedRecord(new 
HoodieKey(rec.get(0).toString(), ""), rec)).collect(Collectors.toList());
   }
diff --git 
a/hudi-common/src/test/java/org/apache/hudi/common/table/read/buffer/TestKeyBasedFileGroupRecordBuffer.java
 
b/hudi-common/src/test/java/org/apache/hudi/common/table/read/buffer/TestKeyBasedFileGroupRecordBuffer.java
index 36149f65e97d..87e8b827f37c 100644
--- 
a/hudi-common/src/test/java/org/apache/hudi/common/table/read/buffer/TestKeyBasedFileGroupRecordBuffer.java
+++ 
b/hudi-common/src/test/java/org/apache/hudi/common/table/read/buffer/TestKeyBasedFileGroupRecordBuffer.java
@@ -123,8 +123,7 @@ class TestKeyBasedFileGroupRecordBuffer extends 
BaseTestFileGroupRecordBuffer {
     
when(dataBlock2.getEngineRecordIterator(readerContext)).thenReturn(ClosableIterator.wrap(Arrays.asList(testRecord2EarlierUpdate,
 testRecord3Update).iterator()));
 
     HoodieDeleteBlock deleteBlock = mock(HoodieDeleteBlock.class);
-    when(deleteBlock.getRecordsToDelete()).thenReturn(new DeleteRecord[] 
{DeleteRecord.create("3", ""), DeleteRecord.create("2", "", -1L),
-        DeleteRecord.create("1", "", 2L)});
+    mockDeleteRecords(deleteBlock, DeleteRecord.create("3", ""), 
DeleteRecord.create("2", "", -1L), DeleteRecord.create("1", "", 2L));
     // process data block, then delete block, then another data block
     fileGroupRecordBuffer.processDataBlock(dataBlock, Option.empty());
     fileGroupRecordBuffer.processDeleteBlock(deleteBlock);
@@ -268,7 +267,7 @@ class TestKeyBasedFileGroupRecordBuffer extends 
BaseTestFileGroupRecordBuffer {
         
.thenReturn(ClosableIterator.wrap(Arrays.asList(testRecord1UpdateWithSameTime, 
testRecord2Delete, testRecord4Update).iterator()));
 
     HoodieDeleteBlock deleteBlock = mock(HoodieDeleteBlock.class);
-    when(deleteBlock.getRecordsToDelete()).thenReturn(new DeleteRecord[] 
{DeleteRecord.create("3", "")});
+    mockDeleteRecords(deleteBlock, DeleteRecord.create("3", ""));
     fileGroupRecordBuffer.processDataBlock(dataBlock1, Option.empty());
     fileGroupRecordBuffer.processDataBlock(dataBlock2, Option.empty());
     fileGroupRecordBuffer.processDeleteBlock(deleteBlock);
@@ -344,7 +343,7 @@ class TestKeyBasedFileGroupRecordBuffer extends 
BaseTestFileGroupRecordBuffer {
         .thenReturn(ClosableIterator.wrap(Arrays.asList(testRecord2Delete, 
testRecord4Update).iterator()));
 
     HoodieDeleteBlock deleteBlock = mock(HoodieDeleteBlock.class);
-    when(deleteBlock.getRecordsToDelete()).thenReturn(new DeleteRecord[] 
{DeleteRecord.create("3", "")});
+    mockDeleteRecords(deleteBlock, DeleteRecord.create("3", ""));
     fileGroupRecordBuffer.processDataBlock(dataBlock1, Option.empty());
     fileGroupRecordBuffer.processDataBlock(dataBlock2, Option.empty());
     fileGroupRecordBuffer.processDeleteBlock(deleteBlock);
diff --git 
a/hudi-common/src/test/java/org/apache/hudi/common/table/read/buffer/TestSortedKeyBasedFileGroupRecordBuffer.java
 
b/hudi-common/src/test/java/org/apache/hudi/common/table/read/buffer/TestSortedKeyBasedFileGroupRecordBuffer.java
index 611128a96309..39ad05de7c5b 100644
--- 
a/hudi-common/src/test/java/org/apache/hudi/common/table/read/buffer/TestSortedKeyBasedFileGroupRecordBuffer.java
+++ 
b/hudi-common/src/test/java/org/apache/hudi/common/table/read/buffer/TestSortedKeyBasedFileGroupRecordBuffer.java
@@ -98,7 +98,7 @@ class TestSortedKeyBasedFileGroupRecordBuffer extends 
BaseTestFileGroupRecordBuf
         ClosableIterator.wrap(Arrays.asList(testRecord6, testRecord4, 
testRecord1, testRecord6Update, testRecord2Update).iterator()));
 
     HoodieDeleteBlock deleteBlock = mock(HoodieDeleteBlock.class);
-    when(deleteBlock.getRecordsToDelete()).thenReturn(new DeleteRecord[] 
{DeleteRecord.create("3", "")});
+    mockDeleteRecords(deleteBlock, DeleteRecord.create("3", ""));
     fileGroupRecordBuffer.processDataBlock(dataBlock, Option.empty());
     fileGroupRecordBuffer.processDeleteBlock(deleteBlock);
 
@@ -175,7 +175,7 @@ class TestSortedKeyBasedFileGroupRecordBuffer extends 
BaseTestFileGroupRecordBuf
     
when(dataBlock2.getEngineRecordIterator(mockReaderContext)).thenReturn(ClosableIterator.wrap(Arrays.asList(testRecord2Update,
 testRecord5, testRecord3, testRecord1).iterator()));
 
     HoodieDeleteBlock deleteBlock = mock(HoodieDeleteBlock.class);
-    when(deleteBlock.getRecordsToDelete()).thenReturn(new DeleteRecord[] 
{DeleteRecord.create("3", "")});
+    mockDeleteRecords(deleteBlock, DeleteRecord.create("3", ""));
     fileGroupRecordBuffer.processDataBlock(dataBlock1, Option.empty());
     fileGroupRecordBuffer.processDataBlock(dataBlock2, Option.empty());
     fileGroupRecordBuffer.processDeleteBlock(deleteBlock);
@@ -217,4 +217,4 @@ class TestSortedKeyBasedFileGroupRecordBuffer extends 
BaseTestFileGroupRecordBuf
     }
     return actualRecords;
   }
-}
\ No newline at end of file
+}
diff --git 
a/hudi-hadoop-common/src/test/java/org/apache/hudi/common/table/TestTableSchemaResolver.java
 
b/hudi-hadoop-common/src/test/java/org/apache/hudi/common/table/TestTableSchemaResolver.java
index f2c6d2b49b20..e296f0d86373 100644
--- 
a/hudi-hadoop-common/src/test/java/org/apache/hudi/common/table/TestTableSchemaResolver.java
+++ 
b/hudi-hadoop-common/src/test/java/org/apache/hudi/common/table/TestTableSchemaResolver.java
@@ -20,6 +20,7 @@ package org.apache.hudi.common.table;
 
 import org.apache.hudi.common.fs.FSUtils;
 import org.apache.hudi.common.model.HoodieCommitMetadata;
+import org.apache.hudi.common.model.HoodieFileFormat;
 import org.apache.hudi.common.model.HoodieLogFile;
 import org.apache.hudi.common.model.HoodieTableType;
 import org.apache.hudi.common.model.HoodieWriteStat;
@@ -28,6 +29,7 @@ import org.apache.hudi.common.schema.HoodieSchemaField;
 import org.apache.hudi.common.schema.HoodieSchemaType;
 import org.apache.hudi.common.table.log.HoodieLogFormat;
 import org.apache.hudi.common.table.log.HoodieLogFormatWriter;
+import org.apache.hudi.common.table.log.NativeLogFooterMetadata;
 import org.apache.hudi.common.table.log.block.HoodieDataBlock;
 import org.apache.hudi.common.table.log.block.HoodieLogBlock;
 import org.apache.hudi.common.table.timeline.HoodieInstant;
@@ -36,8 +38,10 @@ import 
org.apache.hudi.common.table.timeline.versioning.v2.InstantComparatorV2;
 import org.apache.hudi.common.testutils.HoodieTestDataGenerator;
 import org.apache.hudi.common.testutils.HoodieTestUtils;
 import org.apache.hudi.common.testutils.SchemaTestUtil;
+import org.apache.hudi.common.testutils.reader.HoodieFileSliceTestUtils;
 import org.apache.hudi.common.util.FileFormatUtils;
 import org.apache.hudi.common.util.Option;
+import org.apache.hudi.exception.HoodieIOException;
 import org.apache.hudi.internal.schema.HoodieSchemaException;
 import org.apache.hudi.io.storage.HoodieIOFactory;
 import org.apache.hudi.storage.HoodieStorage;
@@ -54,6 +58,8 @@ import org.mockito.MockedStatic;
 
 import java.io.IOException;
 import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -65,6 +71,8 @@ import static 
org.apache.hudi.common.testutils.HoodieCommonTestHarness.getDataBl
 import static org.apache.hudi.common.testutils.SchemaTestUtil.getSimpleSchema;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -124,6 +132,67 @@ class TestTableSchemaResolver {
         logFilePath));
   }
 
+  @Test
+  void testReadSchemaFromNativeDataLogFile() throws IOException {
+    String testDir = initTestDir("read_schema_from_native_data_log_file");
+    StoragePath partitionPath = new StoragePath(testDir, "partition1");
+    Files.createDirectories(Paths.get(partitionPath.toString()));
+    HoodieSchema expectedSchema = HoodieTestDataGenerator.HOODIE_SCHEMA;
+    StoragePath logFilePath = new StoragePath(partitionPath, 
FSUtils.makeNativeLogFileName(
+        "test-fileid1", "1-0-1", "100", 1, "log", HoodieFileFormat.PARQUET));
+    HoodieFileSliceTestUtils.createNativeDataLogFile(
+        logFilePath.toString(),
+        Arrays.asList(HoodieFileSliceTestUtils.DATA_GEN.generateGenericRecord(
+            "1", "partition1", "rider-1", "driver-1", 1L)),
+        expectedSchema,
+        "100");
+
+    assertEquals(expectedSchema, TableSchemaResolver.readSchemaFromLogFile(
+        HoodieStorageUtils.getStorage(logFilePath, 
HoodieTestUtils.getDefaultStorageConf()),
+        logFilePath));
+  }
+
+  @Test
+  void testReadSchemaFromNativeDeleteLogFileReturnsNull() throws IOException {
+    String testDir = initTestDir("read_schema_from_native_delete_log_file");
+    StoragePath partitionPath = new StoragePath(testDir, "partition1");
+    Files.createDirectories(Paths.get(partitionPath.toString()));
+    HoodieSchema tableSchema = HoodieTestDataGenerator.HOODIE_SCHEMA;
+    StoragePath logFilePath = new StoragePath(partitionPath, 
FSUtils.makeNativeLogFileName(
+        "test-fileid1", "1-0-1", "100", 1, "deletes", 
HoodieFileFormat.PARQUET));
+    HoodieStorage storage = HoodieStorageUtils.getStorage(logFilePath, 
HoodieTestUtils.getDefaultStorageConf());
+    HoodieFileSliceTestUtils.createNativeDeleteLogFile(
+        storage,
+        logFilePath.toString(),
+        Arrays.asList(HoodieFileSliceTestUtils.DATA_GEN.generateGenericRecord(
+            "1", "partition1", "rider-1", "driver-1", 1L)),
+        tableSchema,
+        "100");
+
+    assertNull(TableSchemaResolver.readSchemaFromLogFile(storage, 
logFilePath));
+  }
+
+  @Test
+  void testReadSchemaFromNativeDataLogFileRequiresSchemaFooter() {
+    StoragePath logFilePath = new 
StoragePath("/tmp/test-fileid1_1-0-1_100_1.log.parquet");
+    HoodieStorage storage = mock(HoodieStorage.class);
+    HoodieIOFactory ioFactory = mock(HoodieIOFactory.class);
+    FileFormatUtils fileFormatUtils = mock(FileFormatUtils.class);
+    
when(ioFactory.getFileFormatUtils(HoodieFileFormat.PARQUET)).thenReturn(fileFormatUtils);
+    when(fileFormatUtils.readFooter(storage, false, logFilePath, 
NativeLogFooterMetadata.FOOTER_METADATA_KEY))
+        .thenReturn(new HashMap<>());
+
+    try (MockedStatic<HoodieIOFactory> ioFactoryMockedStatic = 
mockStatic(HoodieIOFactory.class)) {
+      ioFactoryMockedStatic.when(() -> 
HoodieIOFactory.getIOFactory(storage)).thenReturn(ioFactory);
+
+      HoodieIOException exception = assertThrows(HoodieIOException.class,
+          () -> TableSchemaResolver.readSchemaFromLogFile(storage, 
logFilePath));
+      
assertTrue(exception.getMessage().contains(HoodieLogBlock.HeaderMetadataType.SCHEMA.name()));
+      
assertTrue(exception.getMessage().contains(NativeLogFooterMetadata.FOOTER_METADATA_KEY));
+      assertTrue(exception.getMessage().contains(logFilePath.toString()));
+    }
+  }
+
   @Test
   void testHasOperationFieldFileInspectionOrdering() throws IOException {
     StorageConfiguration conf = new HadoopStorageConfiguration(false);
diff --git 
a/hudi-hadoop-common/src/test/java/org/apache/hudi/common/table/read/TestHoodieFileGroupReaderNativeLogs.java
 
b/hudi-hadoop-common/src/test/java/org/apache/hudi/common/table/read/TestHoodieFileGroupReaderNativeLogs.java
new file mode 100644
index 000000000000..49ce1c344aac
--- /dev/null
+++ 
b/hudi-hadoop-common/src/test/java/org/apache/hudi/common/table/read/TestHoodieFileGroupReaderNativeLogs.java
@@ -0,0 +1,338 @@
+/*
+ * 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.hudi.common.table.read;
+
+import org.apache.hudi.avro.HoodieAvroReaderContext;
+import org.apache.hudi.common.config.RecordMergeMode;
+import org.apache.hudi.common.engine.HoodieReaderContext;
+import org.apache.hudi.common.fs.FSUtils;
+import org.apache.hudi.common.model.FileSlice;
+import org.apache.hudi.common.model.HoodieBaseFile;
+import org.apache.hudi.common.model.HoodieFileGroupId;
+import org.apache.hudi.common.model.HoodieLogFile;
+import org.apache.hudi.common.table.HoodieTableConfig;
+import 
org.apache.hudi.common.table.log.block.HoodieLogBlock.HoodieLogBlockType;
+import org.apache.hudi.common.testutils.HoodieTestTable;
+import 
org.apache.hudi.common.testutils.reader.HoodieFileGroupReaderTestHarness;
+import org.apache.hudi.common.testutils.reader.HoodieFileSliceTestUtils;
+import org.apache.hudi.common.util.Option;
+import org.apache.hudi.common.util.collection.ClosableIterator;
+import org.apache.hudi.expression.Literal;
+import org.apache.hudi.expression.Predicate;
+import org.apache.hudi.expression.Predicates;
+import org.apache.hudi.storage.HoodieStorage;
+import org.apache.hudi.storage.HoodieStorageUtils;
+
+import org.apache.avro.generic.IndexedRecord;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import static org.apache.hudi.common.table.HoodieTableConfig.ORDERING_FIELDS;
+import static 
org.apache.hudi.common.table.log.block.HoodieLogBlock.HoodieLogBlockType.PARQUET_DATA_BLOCK;
+import static 
org.apache.hudi.common.testutils.FileCreateUtilsLegacy.logFileName;
+import static 
org.apache.hudi.common.testutils.HoodieTestDataGenerator.AVRO_SCHEMA;
+import static 
org.apache.hudi.common.testutils.HoodieTestDataGenerator.HOODIE_SCHEMA;
+import static 
org.apache.hudi.common.testutils.HoodieTestUtils.DEFAULT_WRITE_TOKEN;
+import static 
org.apache.hudi.common.testutils.reader.HoodieFileSliceTestUtils.ROW_KEY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class TestHoodieFileGroupReaderNativeLogs extends 
HoodieFileGroupReaderTestHarness {
+
+  @Override
+  protected Properties getMetaProps() {
+    Properties metaProps = super.getMetaProps();
+    metaProps.setProperty(HoodieTableConfig.RECORD_MERGE_MODE.key(), 
RecordMergeMode.EVENT_TIME_ORDERING.name());
+    metaProps.setProperty(ORDERING_FIELDS.key(), "timestamp");
+    return metaProps;
+  }
+
+  @BeforeAll
+  public static void setUp() throws IOException {
+    properties.setProperty("hoodie.write.record.merge.mode", 
RecordMergeMode.EVENT_TIME_ORDERING.name());
+    instantTimes = Arrays.asList("001", "002", "003", "004");
+  }
+
+  @BeforeEach
+  public void initialize() throws Exception {
+    setTableName(TestHoodieFileGroupReaderNativeLogs.class.getName());
+    initPath(tableName);
+    initMetaClient();
+    initTestDataGenerator(new String[] {PARTITION_PATH});
+    testTable = HoodieTestTable.of(metaClient);
+    readerContext = new HoodieAvroReaderContext(
+        storageConf, metaClient.getTableConfig(), Option.empty(), 
Option.empty());
+    setUpMockCommits();
+  }
+
+  @Test
+  public void testPureNativeLogFiles() throws IOException {
+    HoodieStorage hoodieStorage = HoodieStorageUtils.getStorage(basePath, 
storageConf);
+    preparePartitionPath();
+
+    HoodieLogFile nativeDataLog1 = createNativeDataLogFile("001", 1, 
records(Arrays.asList("1", "2", "3"), 2L, false));
+    HoodieLogFile nativeDataLog2 = createNativeDataLogFile("002", 2, 
records(Arrays.asList("1", "4"), 4L, false));
+    HoodieLogFile nativeDeleteLog = createNativeDeleteLogFile(hoodieStorage, 
"003", 3, records(Arrays.asList("2"), 3L, true));
+    FileSlice fileSlice = createFileSlice(null, nativeDataLog1, 
nativeDataLog2, nativeDeleteLog);
+
+    assertFileGroupRecords(fileSlice,
+        Arrays.asList("1", "3", "4"),
+        Arrays.asList(4L, 2L, 4L));
+  }
+
+  @Test
+  public void testNativeLogFilesWithKeyFilters() throws IOException {
+    preparePartitionPath();
+
+    HoodieLogFile nativeDataLog1 = createNativeDataLogFile("001", 1, 
records(Arrays.asList("1", "2", "30"), 2L, false));
+    HoodieLogFile nativeDataLog2 = createNativeDataLogFile("002", 2, 
records(Arrays.asList("1", "4", "31"), 4L, false));
+    FileSlice fileSlice = createFileSlice(null, nativeDataLog1, 
nativeDataLog2);
+
+    Predicate inPredicate = Predicates.in(
+        Literal.from(ROW_KEY),
+        Arrays.asList(Literal.from("1"), Literal.from("31")));
+    assertFileGroupRecords(fileSlice, inPredicate,
+        Arrays.asList("1", "31"),
+        Arrays.asList(4L, 4L));
+
+    Predicate prefixPredicate = Predicates.startsWithAny(
+        Literal.from(ROW_KEY),
+        Arrays.asList(Literal.from("3")));
+    assertFileGroupRecords(fileSlice, prefixPredicate,
+        Arrays.asList("30", "31"),
+        Arrays.asList(2L, 4L));
+  }
+
+  @Test
+  public void testBaseFileWithNativeLogFiles() throws IOException {
+    preparePartitionPath();
+
+    HoodieBaseFile baseFile = createBaseFile("001", records(Arrays.asList("1", 
"2", "3"), 2L, false));
+    HoodieLogFile nativeDataLog1 = createNativeDataLogFile("002", 1, 
records(Arrays.asList("2"), 4L, false));
+    HoodieLogFile nativeDataLog2 = createNativeDataLogFile("003", 2, 
records(Arrays.asList("4"), 3L, false));
+    FileSlice fileSlice = createFileSlice(baseFile, nativeDataLog1, 
nativeDataLog2);
+
+    assertFileGroupRecords(fileSlice,
+        Arrays.asList("1", "2", "3", "4"),
+        Arrays.asList(2L, 4L, 2L, 3L));
+  }
+
+  @Test
+  public void testBaseFileWithNativeDeleteFiles() throws IOException {
+    HoodieStorage hoodieStorage = HoodieStorageUtils.getStorage(basePath, 
storageConf);
+    preparePartitionPath();
+
+    HoodieBaseFile baseFile = createBaseFile("001", records(Arrays.asList("1", 
"2", "3", "4", "5"), 2L, false));
+    HoodieLogFile nativeDeleteLog1 = createNativeDeleteLogFile(hoodieStorage, 
"002", 1, records(Arrays.asList("1", "2"), Arrays.asList(3L, 1L), true));
+    HoodieLogFile nativeDeleteLog2 = createNativeDeleteLogFile(hoodieStorage, 
"003", 2, records(Arrays.asList("4"), 3L, true));
+    FileSlice fileSlice = createFileSlice(baseFile, nativeDeleteLog1, 
nativeDeleteLog2);
+
+    assertFileGroupRecords(fileSlice,
+        Arrays.asList("2", "3", "5"),
+        Arrays.asList(2L, 2L, 2L));
+  }
+
+  @Test
+  public void testMixedNativeAndLegacyLogFiles() throws IOException, 
InterruptedException {
+    HoodieStorage hoodieStorage = HoodieStorageUtils.getStorage(basePath, 
storageConf);
+    preparePartitionPath();
+
+    HoodieBaseFile baseFile = createBaseFile("001", records(Arrays.asList("1", 
"2", "3", "4"), 2L, false));
+    Map<String, Long> keyToPositionMap = keyToPositionMap("1", "2", "3", "4", 
"5");
+    HoodieLogFile legacyDataLog = createLegacyLogFile(
+        hoodieStorage, "002", 1, records(Arrays.asList("1", "5"), 3L, false),
+        PARQUET_DATA_BLOCK, keyToPositionMap);
+    HoodieLogFile nativeDataLog = createNativeDataLogFile("003", 2, 
records(Arrays.asList("2", "6"), 4L, false));
+    FileSlice fileSlice = createFileSlice(baseFile, legacyDataLog, 
nativeDataLog);
+
+    assertFileGroupRecords(fileSlice,
+        Arrays.asList("1", "2", "3", "4", "5", "6"),
+        Arrays.asList(3L, 4L, 2L, 2L, 3L, 4L));
+  }
+
+  @Test
+  public void testMixedLegacyLogFileAndNativeDeleteFile() throws IOException, 
InterruptedException {
+    HoodieStorage hoodieStorage = HoodieStorageUtils.getStorage(basePath, 
storageConf);
+    preparePartitionPath();
+
+    HoodieBaseFile baseFile = createBaseFile("001", records(Arrays.asList("1", 
"2", "3", "4"), 2L, false));
+    Map<String, Long> keyToPositionMap = keyToPositionMap("1", "2", "3", "4", 
"5");
+    HoodieLogFile legacyDataLog = createLegacyLogFile(
+        hoodieStorage, "002", 1, records(Arrays.asList("1", "5"), 4L, false),
+        PARQUET_DATA_BLOCK, keyToPositionMap);
+    HoodieLogFile nativeDeleteLog = createNativeDeleteLogFile(
+        hoodieStorage, "003", 2, records(Arrays.asList("1", "3", "5"), 
Arrays.asList(3L, 3L, 5L), true));
+    FileSlice fileSlice = createFileSlice(baseFile, legacyDataLog, 
nativeDeleteLog);
+
+    assertFileGroupRecords(fileSlice,
+        Arrays.asList("1", "2", "4"),
+        Arrays.asList(4L, 2L, 2L));
+  }
+
+  @Test
+  public void testMixedLegacyAndNativeLogsPreserveEventTimeDeleteOrdering() 
throws IOException, InterruptedException {
+    HoodieStorage hoodieStorage = HoodieStorageUtils.getStorage(basePath, 
storageConf);
+    FileSlice fileSlice = 
HoodieFileSliceTestUtils.getMixedLegacyAndNativeFileSlice(
+        hoodieStorage, basePath, PARTITION_PATH, FILE_ID)
+        .orElseThrow(() -> new IllegalArgumentException("FileSlice is not 
present"));
+
+    assertFileGroupRecords(fileSlice,
+        Arrays.asList("1", "2", "6", "7", "8", "9", "10"),
+        Arrays.asList(4L, 4L, 2L, 2L, 2L, 2L, 2L));
+  }
+
+  private void assertFileGroupRecords(FileSlice fileSlice, List<String> 
expectedKeys, List<Long> expectedTimestamps)
+      throws IOException {
+    try (ClosableIterator<IndexedRecord> iterator = 
getFileGroupIterator(fileSlice, false, false)) {
+      assertRecords(iterator, expectedKeys, expectedTimestamps);
+    }
+  }
+
+  private void assertFileGroupRecords(FileSlice fileSlice, Predicate 
predicate, List<String> expectedKeys, List<Long> expectedTimestamps)
+      throws IOException {
+    try (ClosableIterator<IndexedRecord> iterator = 
getFileGroupIterator(fileSlice, predicate)) {
+      assertRecords(iterator, expectedKeys, expectedTimestamps);
+    }
+  }
+
+  private static void assertRecords(ClosableIterator<IndexedRecord> iterator, 
List<String> expectedKeys, List<Long> expectedTimestamps) {
+    List<String> actualKeys = new ArrayList<>();
+    List<Long> actualTimestamps = new ArrayList<>();
+    while (iterator.hasNext()) {
+      IndexedRecord record = iterator.next();
+      
actualKeys.add(record.get(AVRO_SCHEMA.getField(ROW_KEY).pos()).toString());
+      actualTimestamps.add((Long) 
record.get(AVRO_SCHEMA.getField("timestamp").pos()));
+    }
+    assertEquals(expectedKeys, actualKeys);
+    assertEquals(expectedTimestamps, actualTimestamps);
+  }
+
+  private ClosableIterator<IndexedRecord> getFileGroupIterator(FileSlice 
fileSlice, Predicate predicate)
+      throws IOException {
+    HoodieReaderContext<IndexedRecord> originalReaderContext = readerContext;
+    readerContext = new HoodieAvroReaderContext(
+        storageConf, metaClient.getTableConfig(), Option.empty(), 
Option.of(predicate));
+    try {
+      return getFileGroupIterator(fileSlice, false, false);
+    } finally {
+      readerContext = originalReaderContext;
+    }
+  }
+
+  private static FileSlice createFileSlice(HoodieBaseFile baseFile, 
HoodieLogFile... logFiles) {
+    return new FileSlice(new HoodieFileGroupId(PARTITION_PATH, FILE_ID),
+        baseFile == null ? null : baseFile.getCommitTime(),
+        baseFile,
+        Arrays.asList(logFiles));
+  }
+
+  private HoodieBaseFile createBaseFile(String instantTime, 
List<IndexedRecord> records) throws IOException {
+    return HoodieFileSliceTestUtils.createBaseFile(
+        partitionBasePath() + "/" + FSUtils.makeBaseFileName(instantTime, 
DEFAULT_WRITE_TOKEN, FILE_ID, ".parquet"),
+        records,
+        HOODIE_SCHEMA,
+        instantTime);
+  }
+
+  private HoodieLogFile createNativeDataLogFile(String instantTime, int 
version, List<IndexedRecord> records)
+      throws IOException {
+    return HoodieFileSliceTestUtils.createNativeDataLogFile(
+        partitionBasePath() + "/" + nativeLogFileName(instantTime, version, 
".log.parquet"),
+        records,
+        HOODIE_SCHEMA,
+        instantTime);
+  }
+
+  private HoodieLogFile createNativeDeleteLogFile(
+      HoodieStorage hoodieStorage, String instantTime, int version, 
List<IndexedRecord> records) throws IOException {
+    return HoodieFileSliceTestUtils.createNativeDeleteLogFile(
+        hoodieStorage,
+        partitionBasePath() + "/" + nativeLogFileName(instantTime, version, 
".deletes.parquet"),
+        records,
+        HOODIE_SCHEMA,
+        instantTime);
+  }
+
+  private HoodieLogFile createLegacyLogFile(
+      HoodieStorage hoodieStorage,
+      String instantTime,
+      int version,
+      List<IndexedRecord> records,
+      HoodieLogBlockType blockType,
+      Map<String, Long> keyToPositionMap
+  ) throws IOException, InterruptedException {
+    return HoodieFileSliceTestUtils.createLogFile(
+        hoodieStorage,
+        partitionBasePath() + "/" + logFileName(instantTime, FILE_ID, version),
+        records,
+        HOODIE_SCHEMA,
+        FILE_ID,
+        "001",
+        instantTime,
+        version,
+        blockType,
+        false,
+        keyToPositionMap);
+  }
+
+  private static Map<String, Long> keyToPositionMap(String... keys) {
+    Map<String, Long> keyToPositionMap = new HashMap<>();
+    for (int i = 0; i < keys.length; i++) {
+      keyToPositionMap.put(keys[i], (long) i);
+    }
+    return keyToPositionMap;
+  }
+
+  private void preparePartitionPath() {
+    new File(partitionBasePath()).mkdirs();
+  }
+
+  private String partitionBasePath() {
+    return basePath + "/" + PARTITION_PATH;
+  }
+
+  private static String nativeLogFileName(String instantTime, int version, 
String extension) {
+    return String.format("%s_%s_%s_%d%s", FILE_ID, DEFAULT_WRITE_TOKEN, 
instantTime, version, extension);
+  }
+
+  private static List<IndexedRecord> records(List<String> keys, long 
timestamp, boolean isDelete) {
+    List<Long> timestamps = new ArrayList<>();
+    keys.forEach(key -> timestamps.add(timestamp));
+    return records(keys, timestamps, isDelete);
+  }
+
+  private static List<IndexedRecord> records(List<String> keys, List<Long> 
timestamps, boolean isDelete) {
+    List<IndexedRecord> records = new ArrayList<>();
+    for (int i = 0; i < keys.size(); i++) {
+      records.add(HoodieFileSliceTestUtils.DATA_GEN.generateGenericRecord(
+          keys.get(i), PARTITION_PATH, "rider." + keys.get(i), "driver." + 
keys.get(i), timestamps.get(i), isDelete, false));
+    }
+    return records;
+  }
+}
diff --git 
a/hudi-hadoop-common/src/test/java/org/apache/hudi/common/table/view/TestHoodieTableFileSystemView.java
 
b/hudi-hadoop-common/src/test/java/org/apache/hudi/common/table/view/TestHoodieTableFileSystemView.java
index ff9e303f7772..082cdbf2767b 100644
--- 
a/hudi-hadoop-common/src/test/java/org/apache/hudi/common/table/view/TestHoodieTableFileSystemView.java
+++ 
b/hudi-hadoop-common/src/test/java/org/apache/hudi/common/table/view/TestHoodieTableFileSystemView.java
@@ -2504,6 +2504,38 @@ public class TestHoodieTableFileSystemView extends 
HoodieCommonTestHarness {
     return writeStat;
   }
 
+  @Test
+  public void testNativeLogFilesAreVisibleInFileSystemView() throws Exception {
+    String partitionPath = "2026/06/25";
+    new File(basePath + "/" + partitionPath).mkdirs();
+
+    String fileId = UUID.randomUUID().toString();
+    String instantTime = "001";
+    String nativeDataLogFileName = String.format("%s_%s_%s_%d.log.parquet", 
fileId, TEST_WRITE_TOKEN, instantTime, 1);
+    String nativeDeleteLogFileName = 
String.format("%s_%s_%s_%d.deletes.parquet", fileId, TEST_WRITE_TOKEN, 
instantTime, 2);
+    String nativeCdcLogFileName = String.format("%s_%s_%s_%d.cdc.parquet", 
fileId, TEST_WRITE_TOKEN, instantTime, 3);
+    new File(basePath + "/" + partitionPath + "/" + 
nativeDataLogFileName).createNewFile();
+    new File(basePath + "/" + partitionPath + "/" + 
nativeDeleteLogFileName).createNewFile();
+    new File(basePath + "/" + partitionPath + "/" + 
nativeCdcLogFileName).createNewFile();
+
+    HoodieCommitMetadata commitMetadata = new HoodieCommitMetadata();
+    commitMetadata.addWriteStat(partitionPath, 
getHoodieWriteStat(partitionPath, fileId, nativeDataLogFileName));
+    commitMetadata.addWriteStat(partitionPath, 
getHoodieWriteStat(partitionPath, fileId, nativeDeleteLogFileName));
+    commitMetadata.addWriteStat(partitionPath, 
getHoodieWriteStat(partitionPath, fileId, nativeCdcLogFileName));
+    HoodieInstant instant = INSTANT_GENERATOR.createNewInstant(State.INFLIGHT, 
HoodieTimeline.DELTA_COMMIT_ACTION, instantTime);
+    saveAsComplete(metaClient.getActiveTimeline(), instant, commitMetadata);
+
+    try (SyncableFileSystemView fileSystemView = 
getFileSystemView(metaClient.reloadActiveTimeline(), true)) {
+      Set<String> logFileNames = fileSystemView.getAllFileSlices(partitionPath)
+          .flatMap(FileSlice::getLogFiles)
+          .map(logFile -> logFile.getPath().getName())
+          .collect(Collectors.toSet());
+      assertTrue(logFileNames.contains(nativeDataLogFileName));
+      assertTrue(logFileNames.contains(nativeDeleteLogFileName));
+      assertTrue(logFileNames.contains(nativeCdcLogFileName));
+    }
+  }
+
   @Test
   public void testGetLatestMergedFileSlicesBeforeOrOnIncludingInflight() 
throws IOException {
     String partitionPath = "2023/01/01";
diff --git 
a/hudi-hadoop-common/src/test/java/org/apache/hudi/common/testutils/reader/HoodieFileGroupReaderTestHarness.java
 
b/hudi-hadoop-common/src/test/java/org/apache/hudi/common/testutils/reader/HoodieFileGroupReaderTestHarness.java
index 1ec6d41d5b87..da95c1d6acfc 100644
--- 
a/hudi-hadoop-common/src/test/java/org/apache/hudi/common/testutils/reader/HoodieFileGroupReaderTestHarness.java
+++ 
b/hudi-hadoop-common/src/test/java/org/apache/hudi/common/testutils/reader/HoodieFileGroupReaderTestHarness.java
@@ -133,11 +133,16 @@ public class HoodieFileGroupReaderTestHarness extends 
HoodieCommonTestHarness {
             FILE_ID
         );
 
+    FileSlice fileSlice = fileSliceOpt.orElseThrow(() -> new 
IllegalArgumentException("FileSlice is not present"));
+    return getFileGroupIterator(fileSlice, shouldReadPositions, 
allowInflightCommits);
+  }
+
+  protected ClosableIterator<IndexedRecord> getFileGroupIterator(FileSlice 
fileSlice, boolean shouldReadPositions, boolean allowInflightCommits)
+      throws IOException {
     
properties.setProperty(HoodieMemoryConfig.MAX_MEMORY_FOR_MERGE.key(),String.valueOf(1024
 * 1024 * 1000));
     properties.setProperty(HoodieMemoryConfig.SPILLABLE_MAP_BASE_PATH.key(),  
basePath + "/" + HoodieTableMetaClient.TEMPFOLDER_NAME);
     properties.setProperty(HoodieCommonConfig.SPILLABLE_DISK_MAP_TYPE.key(), 
ExternalSpillableMap.DiskMapType.ROCKS_DB.name());
     
properties.setProperty(HoodieCommonConfig.DISK_MAP_BITCASK_COMPRESSION_ENABLED.key(),
 "false");
-    FileSlice fileSlice = fileSliceOpt.orElseThrow(() -> new 
IllegalArgumentException("FileSlice is not present"));
     HoodieFileGroupReader<IndexedRecord> fileGroupReader = 
HoodieFileGroupReader.<IndexedRecord>builder()
         .withReaderContext(readerContext)
         .withHoodieTableMetaClient(metaClient)
diff --git 
a/hudi-hadoop-common/src/test/java/org/apache/hudi/common/testutils/reader/HoodieFileSliceTestUtils.java
 
b/hudi-hadoop-common/src/test/java/org/apache/hudi/common/testutils/reader/HoodieFileSliceTestUtils.java
index d0f5bde6f41e..0cd11d770c98 100644
--- 
a/hudi-hadoop-common/src/test/java/org/apache/hudi/common/testutils/reader/HoodieFileSliceTestUtils.java
+++ 
b/hudi-hadoop-common/src/test/java/org/apache/hudi/common/testutils/reader/HoodieFileSliceTestUtils.java
@@ -19,8 +19,10 @@
 
 package org.apache.hudi.common.testutils.reader;
 
+import org.apache.hudi.avro.HoodieAvroWriteSupport;
 import org.apache.hudi.common.bloom.BloomFilterTypeCode;
 import org.apache.hudi.common.config.HoodieConfig;
+import org.apache.hudi.common.config.HoodieParquetConfig;
 import org.apache.hudi.common.config.HoodieStorageConfig;
 import org.apache.hudi.common.config.TypedProperties;
 import org.apache.hudi.common.engine.LocalTaskContextSupplier;
@@ -34,9 +36,11 @@ import org.apache.hudi.common.model.HoodieLogFile;
 import org.apache.hudi.common.model.HoodieRecord;
 import org.apache.hudi.common.model.HoodieRecordLocation;
 import org.apache.hudi.common.schema.HoodieSchema;
+import org.apache.hudi.common.schema.HoodieSchemas;
 import org.apache.hudi.common.table.HoodieTableConfig;
 import org.apache.hudi.common.table.log.HoodieLogFormat;
 import org.apache.hudi.common.table.log.HoodieLogFormatWriter;
+import org.apache.hudi.common.table.log.NativeLogFooterMetadata;
 import org.apache.hudi.common.table.log.block.HoodieAvroDataBlock;
 import org.apache.hudi.common.table.log.block.HoodieCDCDataBlock;
 import org.apache.hudi.common.table.log.block.HoodieDataBlock;
@@ -50,16 +54,22 @@ import org.apache.hudi.common.util.Option;
 import org.apache.hudi.common.util.collection.Pair;
 import org.apache.hudi.io.storage.HoodieAvroFileWriter;
 import org.apache.hudi.io.storage.HoodieFileWriterFactory;
+import org.apache.hudi.io.storage.hadoop.HoodieAvroParquetWriter;
 import org.apache.hudi.storage.HoodieStorage;
 import org.apache.hudi.storage.StoragePath;
 
+import org.apache.avro.generic.GenericData;
+import org.apache.avro.generic.GenericRecord;
 import org.apache.avro.generic.IndexedRecord;
+import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.Path;
 import org.apache.parquet.hadoop.ParquetWriter;
 import org.apache.parquet.hadoop.metadata.CompressionCodecName;
 
+import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -79,6 +89,7 @@ import static 
org.apache.hudi.common.testutils.HoodieTestDataGenerator.AVRO_SCHE
 import static 
org.apache.hudi.common.testutils.HoodieTestDataGenerator.HOODIE_SCHEMA;
 import static 
org.apache.hudi.common.testutils.reader.DataGenerationPlan.OperationType.DELETE;
 import static 
org.apache.hudi.common.testutils.reader.DataGenerationPlan.OperationType.INSERT;
+import static 
org.apache.parquet.avro.HoodieAvroParquetSchemaConverter.getAvroSchemaConverter;
 
 public class HoodieFileSliceTestUtils {
   public static final String FORWARD_SLASH = "/";
@@ -93,6 +104,7 @@ public class HoodieFileSliceTestUtils {
   public static final HoodieTestDataGenerator DATA_GEN =
       new HoodieTestDataGenerator(0xDEED);
   public static final TypedProperties PROPERTIES = new TypedProperties();
+  private static final String WRITE_TOKEN = "1-0-1";
   private static String[] orderingFields = new String[] {"timestamp"};
 
   // We use a number to represent a record key, and a (start, end) range
@@ -127,6 +139,16 @@ public class HoodieFileSliceTestUtils {
         instantTime, fileId, version));
   }
 
+  private static Path generateNativeLogFilePath(
+      String basePath,
+      String fileId,
+      String instantTime,
+      int version,
+      String extension) {
+    return new Path(basePath + FORWARD_SLASH
+        + String.format("%s_%s_%s_%d%s", fileId, WRITE_TOKEN, instantTime, 
version, extension));
+  }
+
   // Note:
   // "start < end" means start <= k <= end.
   // "start == end" means k = start.
@@ -318,6 +340,85 @@ public class HoodieFileSliceTestUtils {
     return new HoodieLogFile(logFilePath);
   }
 
+  public static HoodieLogFile createNativeDataLogFile(
+      String logFilePath,
+      List<IndexedRecord> records,
+      HoodieSchema schema,
+      String logInstantTime
+  ) throws IOException {
+    HoodieStorage storage = HoodieTestUtils.getStorage(logFilePath);
+    try (HoodieAvroFileWriter writer = createNativeLogWriter(storage, 
logFilePath, schema, logInstantTime)) {
+      for (IndexedRecord record : records) {
+        writer.writeAvro(
+            (String) record.get(schema.getField(ROW_KEY).get().pos()), record);
+      }
+    }
+    return new HoodieLogFile(logFilePath);
+  }
+
+  public static HoodieLogFile createNativeDeleteLogFile(
+      HoodieStorage storage,
+      String logFilePath,
+      List<IndexedRecord> records,
+      HoodieSchema tableSchema,
+      String logInstantTime
+  ) throws IOException {
+    HoodieSchema deleteLogSchema = 
HoodieSchemas.createDeleteLogSchema(tableSchema, Arrays.asList(TIMESTAMP));
+    try (HoodieAvroFileWriter writer = createNativeLogWriter(storage, 
logFilePath, deleteLogSchema, logInstantTime)) {
+      for (IndexedRecord record : records) {
+        String recordKey = 
record.get(record.getSchema().getField(ROW_KEY).pos()).toString();
+        GenericRecord deleteRecord = new 
GenericData.Record(deleteLogSchema.toAvroSchema());
+        deleteRecord.put(HoodieRecord.RECORD_KEY_METADATA_FIELD, recordKey);
+        deleteRecord.put(TIMESTAMP, 
record.get(record.getSchema().getField(TIMESTAMP).pos()));
+        writer.writeAvro(recordKey, deleteRecord);
+      }
+    }
+    return new HoodieLogFile(logFilePath);
+  }
+
+  private static HoodieAvroFileWriter createNativeLogWriter(
+      HoodieStorage storage,
+      String logFilePath,
+      HoodieSchema schema,
+      String logInstantTime
+  ) throws IOException {
+    HoodieAvroWriteSupport writeSupport = new HoodieAvroWriteSupport(
+        
getAvroSchemaConverter(storage.getConf().unwrapAs(Configuration.class)).convert(schema),
+        schema,
+        Option.empty(),
+        new Properties());
+    // Write the footer using the same shared serializer the real 
HoodieNativeLogFormatWriter uses,
+    // so the read path is exercised against the actual on-disk contract.
+    NativeLogFooterMetadata.toFooterMetadata(getNativeLogBlockHeader(schema, 
logInstantTime))
+        .forEach(writeSupport::addFooterMetadata);
+
+    HoodieParquetConfig<HoodieAvroWriteSupport> parquetConfig = new 
HoodieParquetConfig<>(
+        writeSupport,
+        CompressionCodecName.GZIP,
+        ParquetWriter.DEFAULT_BLOCK_SIZE,
+        ParquetWriter.DEFAULT_PAGE_SIZE,
+        1024 * 1024 * 1024,
+        storage.getConf(),
+        0.1,
+        true);
+    return new HoodieAvroParquetWriter(
+        new StoragePath(logFilePath),
+        parquetConfig,
+        logInstantTime,
+        new LocalTaskContextSupplier(),
+        false);
+  }
+
+  private static Map<HoodieLogBlock.HeaderMetadataType, String> 
getNativeLogBlockHeader(
+      HoodieSchema schema,
+      String logInstantTime
+  ) {
+    Map<HoodieLogBlock.HeaderMetadataType, String> header = new HashMap<>();
+    header.put(HoodieLogBlock.HeaderMetadataType.INSTANT_TIME, logInstantTime);
+    header.put(HoodieLogBlock.HeaderMetadataType.SCHEMA, schema.toString());
+    return header;
+  }
+
   /**
    * Based on provided parameters to generate a {@link FileSlice} object.
    */
@@ -452,4 +553,90 @@ public class HoodieFileSliceTestUtils {
         HOODIE_SCHEMA,
         plans));
   }
+
+  /**
+   * Generate a file slice mixing legacy inline logs with RFC-103 native 
parquet data and delete logs.
+   */
+  public static Option<FileSlice> getMixedLegacyAndNativeFileSlice(
+      HoodieStorage storage,
+      String basePath,
+      String partitionPath,
+      String fileId
+  ) throws IOException, InterruptedException {
+    String partitionBasePath = basePath + FORWARD_SLASH + partitionPath;
+    new File(partitionBasePath).mkdirs();
+
+    Map<String, Long> keyToPositionMap = new HashMap<>();
+    DataGenerationPlan baseFilePlan = DataGenerationPlan.newBuilder()
+        .withOperationType(INSERT)
+        .withPartitionPath(partitionPath)
+        .withRecordKeys(generateKeys(new KeyRange(1, 10)))
+        .withTimeStamp(2L)
+        .withInstantTime("001")
+        .withWritePositions(false)
+        .build();
+    List<IndexedRecord> baseRecords = generateRecords(baseFilePlan);
+    HoodieBaseFile baseFile = createBaseFile(
+        generateBaseFilePath(partitionBasePath, fileId, 
baseFilePlan.getInstantTime()).toString(),
+        baseRecords,
+        HOODIE_SCHEMA,
+        baseFilePlan.getInstantTime());
+    for (int i = 0; i < baseFilePlan.getRecordKeys().size(); i++) {
+      keyToPositionMap.put(baseFilePlan.getRecordKeys().get(i), (long) i);
+    }
+
+    List<HoodieLogFile> logFiles = new ArrayList<>();
+    DataGenerationPlan legacyDeletePlan = DataGenerationPlan.newBuilder()
+        .withOperationType(DELETE)
+        .withPartitionPath(partitionPath)
+        .withRecordKeys(generateKeys(new KeyRange(1, 5)))
+        .withTimeStamp(3L)
+        .withInstantTime("002")
+        .withWritePositions(false)
+        .build();
+    logFiles.add(createLogFile(
+        storage,
+        generateLogFilePath(partitionBasePath, fileId, 
legacyDeletePlan.getInstantTime(), 1).toString(),
+        generateRecords(legacyDeletePlan),
+        HOODIE_SCHEMA,
+        fileId,
+        baseFilePlan.getInstantTime(),
+        legacyDeletePlan.getInstantTime(),
+        1,
+        DELETE_BLOCK,
+        false,
+        keyToPositionMap));
+
+    DataGenerationPlan nativeDataPlan = DataGenerationPlan.newBuilder()
+        .withOperationType(INSERT)
+        .withPartitionPath(partitionPath)
+        .withRecordKeys(generateKeys(new KeyRange(1, 2)))
+        .withTimeStamp(4L)
+        .withInstantTime("003")
+        .withWritePositions(false)
+        .build();
+    logFiles.add(createNativeDataLogFile(
+        generateNativeLogFilePath(partitionBasePath, fileId, 
nativeDataPlan.getInstantTime(), 2, ".log.parquet").toString(),
+        generateRecords(nativeDataPlan),
+        HOODIE_SCHEMA,
+        nativeDataPlan.getInstantTime()));
+
+    DataGenerationPlan nativeDeletePlan = DataGenerationPlan.newBuilder()
+        .withOperationType(DELETE)
+        .withPartitionPath(partitionPath)
+        .withRecordKeys(generateKeys(new KeyRange(6, 8)))
+        .withTimeStamp(1L)
+        .withInstantTime("004")
+        .withWritePositions(false)
+        .build();
+    logFiles.add(createNativeDeleteLogFile(
+        storage,
+        generateNativeLogFilePath(partitionBasePath, fileId, 
nativeDeletePlan.getInstantTime(), 3, ".deletes.parquet").toString(),
+        generateRecords(nativeDeletePlan),
+        HOODIE_SCHEMA,
+        nativeDeletePlan.getInstantTime()));
+
+    return Option.of(new FileSlice(
+        new HoodieFileGroupId(partitionPath, fileId), 
baseFile.getCommitTime(), baseFile, logFiles));
+  }
 }

Reply via email to