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

yihua 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 b5263e1a2e63 fix(client): create a new log file version instead of 
reopening the latest on pre-v8 writes (#19755)
b5263e1a2e63 is described below

commit b5263e1a2e63eb8d1414b884a3b9138a9a5460f8
Author: Lokesh Jain <[email protected]>
AuthorDate: Thu Aug 27 05:10:34 2026 +0530

    fix(client): create a new log file version instead of reopening the latest 
on pre-v8 writes (#19755)
---
 .../java/org/apache/hudi/io/HoodieWriteHandle.java |   7 +-
 .../org/apache/hudi/io/TestHoodieAppendHandle.java | 142 +++++++++++++++++++++
 2 files changed, 148 insertions(+), 1 deletion(-)

diff --git 
a/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/HoodieWriteHandle.java
 
b/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/HoodieWriteHandle.java
index fb227e77ee46..7099d82f3ea5 100644
--- 
a/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/HoodieWriteHandle.java
+++ 
b/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/HoodieWriteHandle.java
@@ -311,11 +311,16 @@ public abstract class HoodieWriteHandle<T, I, K, O> 
extends HoodieIOHandle<T, I,
         Option<HoodieLogFile> latestLogFile = fileSliceOpt.isPresent()
             ? fileSliceOpt.get().getLatestLogFile()
             : Option.empty();
+        // Target a new log file version (latest + 1) instead of reopening the 
latest log file.
+        // Creating a fresh version avoids relying on append support and on 
locating the latest log slice.
+        int logFileVersion = latestLogFile
+            .map(logFile -> logFile.getLogVersion() + 1)
+            .orElse(HoodieLogFile.LOGFILE_BASE_VERSION);
         return HoodieLogFormatWriter.builder()
             
.withParentPath(FSUtils.constructAbsolutePath(hoodieTable.getMetaClient().getBasePath(),
 partitionPath))
             .withLogFileId(fileId)
             .withInstantTime(instantTime)
-            
.withLogVersion(latestLogFile.map(HoodieLogFile::getLogVersion).orElse(HoodieLogFile.LOGFILE_BASE_VERSION))
+            .withLogVersion(logFileVersion)
             
.withFileSize(latestLogFile.map(HoodieLogFile::getFileSize).orElse(0L))
             .withSizeThreshold(config.getLogFileMaxSize())
             .withStorage(storage)
diff --git 
a/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/io/TestHoodieAppendHandle.java
 
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/io/TestHoodieAppendHandle.java
new file mode 100644
index 000000000000..b1f194796c62
--- /dev/null
+++ 
b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/io/TestHoodieAppendHandle.java
@@ -0,0 +1,142 @@
+/*
+ * 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.io;
+
+import org.apache.hudi.common.engine.LocalTaskContextSupplier;
+import org.apache.hudi.common.engine.TaskContextSupplier;
+import org.apache.hudi.common.model.FileSlice;
+import org.apache.hudi.common.model.HoodieLogFile;
+import org.apache.hudi.common.table.HoodieTableVersion;
+import org.apache.hudi.common.table.log.HoodieLogFormat;
+import org.apache.hudi.common.table.view.SyncableFileSystemView;
+import org.apache.hudi.common.testutils.HoodieCommonTestHarness;
+import org.apache.hudi.common.util.HoodieStorageUtils;
+import org.apache.hudi.common.util.Option;
+import org.apache.hudi.config.HoodieWriteConfig;
+import org.apache.hudi.table.HoodieTable;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.io.IOException;
+import java.util.stream.Stream;
+
+import static 
org.apache.hudi.common.testutils.HoodieTestDataGenerator.TRIP_EXAMPLE_SCHEMA;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class TestHoodieAppendHandle extends HoodieCommonTestHarness {
+
+  private static final String TEST_INSTANT_TIME = "20231201120000";
+  private static final String TEST_PARTITION_PATH = "partition1";
+  private static final String TEST_FILE_ID = "file-001";
+
+  @Mock
+  private HoodieTable<Object, Object, Object, Object> mockHoodieTable;
+
+  private HoodieWriteConfig writeConfig;
+
+  private TaskContextSupplier taskContextSupplier;
+
+  @BeforeEach
+  public void setUp() throws IOException {
+    initPath();
+    initMetaClient();
+
+    writeConfig = HoodieWriteConfig.newBuilder()
+        .withPath(basePath)
+        .withSchema(TRIP_EXAMPLE_SCHEMA)
+        .withMarkersType("DIRECT")
+        .build();
+    taskContextSupplier = new LocalTaskContextSupplier();
+
+    mockMethodsNeededByConstructor();
+  }
+
+  @AfterEach
+  public void cleanUp() {
+    cleanMetaClient();
+  }
+
+  private void mockMethodsNeededByConstructor() {
+    when(mockHoodieTable.getConfig()).thenReturn(writeConfig);
+    when(mockHoodieTable.getMetaClient()).thenReturn(metaClient);
+  }
+
+  private static Stream<Arguments> versionsSixAndAbove() {
+    return Stream.of(
+        Arguments.of(HoodieTableVersion.SIX),
+        Arguments.of(HoodieTableVersion.EIGHT),
+        Arguments.of(HoodieTableVersion.NINE)
+    );
+  }
+
+  @ParameterizedTest
+  @MethodSource("versionsSixAndAbove")
+  void testCreateLogFileWriterLogVersion(HoodieTableVersion tableVersion) 
throws IOException {
+    writeConfig = HoodieWriteConfig.newBuilder()
+        .withPath(basePath)
+        .withSchema(TRIP_EXAMPLE_SCHEMA)
+        .withMarkersType("DIRECT")
+        .withWriteTableVersion(tableVersion.versionCode())
+        .build();
+
+    storage = HoodieStorageUtils.getStorage(basePath, 
metaClient.getStorageConf());
+
+    when(mockHoodieTable.getStorage()).thenReturn(storage);
+    if (tableVersion.greaterThanOrEquals(HoodieTableVersion.EIGHT)) {
+      SyncableFileSystemView mockedFSView = mock(SyncableFileSystemView.class);
+      when(mockHoodieTable.getHoodieView()).thenReturn(mockedFSView);
+      when(mockedFSView.getLatestBaseFile(TEST_PARTITION_PATH, 
TEST_FILE_ID)).thenReturn(Option.empty());
+    }
+
+    HoodieAppendHandle<Object, Object, Object, Object> appendHandle =
+        new HoodieInlineLogAppendHandle<>(writeConfig, TEST_INSTANT_TIME, 
mockHoodieTable, TEST_PARTITION_PATH, TEST_FILE_ID, taskContextSupplier);
+
+    FileSlice mockFileSlice = mock(FileSlice.class);
+    if (tableVersion.lesserThan(HoodieTableVersion.EIGHT)) {
+      when(mockFileSlice.getLatestLogFile()).thenReturn(Option.empty());
+    }
+
+    // verify writer log version is 1 when there are no log files present
+    try (HoodieLogFormat.Writer writer = 
appendHandle.createLogWriter(TEST_INSTANT_TIME, Option.of(mockFileSlice))) {
+      assertEquals(1, writer.getLogFile().getLogVersion());
+    }
+
+    HoodieLogFile mockLogFile = mock(HoodieLogFile.class);
+    if (tableVersion.lesserThan(HoodieTableVersion.EIGHT)) {
+      when(mockLogFile.getLogVersion()).thenReturn(1);
+      
when(mockFileSlice.getLatestLogFile()).thenReturn(Option.of(mockLogFile));
+    }
+
+    // verify writer log version is incremented (latest + 1) for pre-v8 tables 
when a log file is
+    // present; v8+ derives the log file name differently and is unaffected.
+    try (HoodieLogFormat.Writer writer = 
appendHandle.createLogWriter(TEST_INSTANT_TIME, Option.of(mockFileSlice))) {
+      assertEquals(tableVersion.greaterThanOrEquals(HoodieTableVersion.EIGHT) 
? 1 : 2, writer.getLogFile().getLogVersion());
+    }
+  }
+}

Reply via email to