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

jt2594838 pushed a commit to branch support_wal_in_import_data
in repository https://gitbox.apache.org/repos/asf/iotdb.git

commit a25258ac70aff80e3af284f99208c29db1194390
Author: Tian Jiang <[email protected]>
AuthorDate: Thu Aug 13 17:05:40 2026 +0800

    add on_success
---
 .../apache/iotdb/db/i18n/ImportWALMessages.java    | 10 ++++
 .../apache/iotdb/db/i18n/ImportWALMessages.java    | 10 ++++
 .../java/org/apache/iotdb/db/tools/ImportWAL.java  | 61 ++++++++++++++++++++-
 .../org/apache/iotdb/db/tools/ImportWALTest.java   | 63 ++++++++++++++++++++++
 4 files changed, 143 insertions(+), 1 deletion(-)

diff --git 
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/ImportWALMessages.java
 
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/ImportWALMessages.java
index 2ed53656ec0..0dd487bfe8b 100644
--- 
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/ImportWALMessages.java
+++ 
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/ImportWALMessages.java
@@ -38,6 +38,9 @@ public final class ImportWALMessages {
   public static final String MESSAGE_PASSWORD_PROMPT_F2D0E794 = "Password: ";
   public static final String 
MESSAGE_TARGET_DATABASE_FOR_TABLE_MODEL_WAL_ENTRIES_27BACD1C =
       "Target database for table-model WAL entries.";
+  public static final String
+      
MESSAGE_WHEN_ALL_WAL_FILES_ARE_REPLAYED_SUCCESSFULLY_DO_OPERATION_ON_SOURCE_WAL_FILES_OPTIONAL_PARAMETERS_ARE_NONE_DEFAULT_AND_DELETE_41963A66
 =
+          "When all WAL files are replayed successfully, do operation on 
source WAL files. Optional parameters are none (default) and delete.";
   public static final String MESSAGE_PRINT_THIS_HELP_MESSAGE_E800AF7A =
       "Print this help message.";
   public static final String MESSAGE_ARGUMENT_ERROR_ARG_A9767F62 = "Argument 
error: %s";
@@ -58,8 +61,15 @@ public final class ImportWALMessages {
   public static final String
       
MESSAGE_IMPORT_DURATION_ARG_SECONDS_TOTAL_SIZE_ARG_BYTES_AVERAGE_RATE_ARG_MB_PER_SECOND_4B4EA58D
 =
           "Import duration: %.1f s; total size: %d bytes; average rate: %.1f 
MB/s.";
+  public static final String MESSAGE_DELETED_ARG_SOURCE_WAL_FILES_C7A5AA1B =
+      "Deleted %d source WAL files.";
   public static final String 
EXCEPTION_FAILED_TO_REPLAY_WAL_FILE_ARG_AT_OFFSET_ARG_ARG_FCFAF7F9 =
       "Failed to replay WAL file %s at offset %d: %s";
+  public static final String 
EXCEPTION_FAILED_TO_DELETE_SOURCE_WAL_FILE_ARG_ARG_236AF580 =
+      "Failed to delete source WAL file %s: %s";
+  public static final String
+      
EXCEPTION_UNSUPPORTED_ON_SUCCESS_VALUE_ARG_EXPECTED_NONE_OR_DELETE_F1C8EACE =
+          "Unsupported on_success value: %s. Expected none or delete.";
   public static final String 
EXCEPTION_TABLE_MODEL_WAL_ENTRIES_REQUIRE_DB_DATABASE_F7597726 =
       "Table-model WAL entries require -db/--database.";
   public static final String EXCEPTION_UNSUPPORTED_WAL_OPERATION_ARG_ABD227A0 =
diff --git 
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/ImportWALMessages.java
 
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/ImportWALMessages.java
index c8dc2266dc8..57052048e63 100644
--- 
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/ImportWALMessages.java
+++ 
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/ImportWALMessages.java
@@ -38,6 +38,9 @@ public final class ImportWALMessages {
   public static final String MESSAGE_PASSWORD_PROMPT_F2D0E794 = "密码:";
   public static final String 
MESSAGE_TARGET_DATABASE_FOR_TABLE_MODEL_WAL_ENTRIES_27BACD1C =
       "表模型 WAL 条目的目标数据库。";
+  public static final String
+      
MESSAGE_WHEN_ALL_WAL_FILES_ARE_REPLAYED_SUCCESSFULLY_DO_OPERATION_ON_SOURCE_WAL_FILES_OPTIONAL_PARAMETERS_ARE_NONE_DEFAULT_AND_DELETE_41963A66
 =
+          "所有 WAL 文件成功重放后,对源 WAL 文件执行操作。可选参数为 none(默认)和 delete。";
   public static final String MESSAGE_PRINT_THIS_HELP_MESSAGE_E800AF7A = 
"打印帮助信息。";
   public static final String MESSAGE_ARGUMENT_ERROR_ARG_A9767F62 = "参数错误:%s";
   public static final String MESSAGE_WAL_IMPORT_FAILED_ARG_55C014BA = "WAL 
导入失败:%s";
@@ -57,8 +60,15 @@ public final class ImportWALMessages {
   public static final String
       
MESSAGE_IMPORT_DURATION_ARG_SECONDS_TOTAL_SIZE_ARG_BYTES_AVERAGE_RATE_ARG_MB_PER_SECOND_4B4EA58D
 =
           "导入耗时:%.1f 秒;文件总大小:%d 字节;平均速率:%.1f MB/s。";
+  public static final String MESSAGE_DELETED_ARG_SOURCE_WAL_FILES_C7A5AA1B =
+      "已删除 %d 个源 WAL 文件。";
   public static final String 
EXCEPTION_FAILED_TO_REPLAY_WAL_FILE_ARG_AT_OFFSET_ARG_ARG_FCFAF7F9 =
       "重放 WAL 文件 %s 时失败,偏移量 %d:%s";
+  public static final String 
EXCEPTION_FAILED_TO_DELETE_SOURCE_WAL_FILE_ARG_ARG_236AF580 =
+      "删除源 WAL 文件 %s 失败:%s";
+  public static final String
+      
EXCEPTION_UNSUPPORTED_ON_SUCCESS_VALUE_ARG_EXPECTED_NONE_OR_DELETE_F1C8EACE =
+          "不支持的 on_success 值:%s。应为 none 或 delete。";
   public static final String 
EXCEPTION_TABLE_MODEL_WAL_ENTRIES_REQUIRE_DB_DATABASE_F7597726 =
       "表模型 WAL 条目要求指定 -db/--database。";
   public static final String EXCEPTION_UNSUPPORTED_WAL_OPERATION_ARG_ABD227A0 =
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/tools/ImportWAL.java 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/tools/ImportWAL.java
index f8316499bdb..c1281c00ee3 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/tools/ImportWAL.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/tools/ImportWAL.java
@@ -118,6 +118,8 @@ public class ImportWAL {
       final Path source = Paths.get(commandLine.getOptionValue("file"));
       final List<Path> walFiles = collectWALFiles(source);
       final String database = commandLine.getOptionValue("database");
+      final boolean deleteSource =
+          shouldDeleteSource(commandLine.getOptionValue("on_success", "none"));
       final String password = getPassword(commandLine);
       final Session treeSession =
           createSession(
@@ -141,7 +143,8 @@ public class ImportWAL {
           tableSession.open(false);
         }
         final ReplayStatistics statistics =
-            replayWALFiles(walFiles, new WALReplayer(treeSession, 
tableSession, database), out);
+            replayWALFiles(
+                walFiles, new WALReplayer(treeSession, tableSession, 
database), out, deleteSource);
         out.printf(
             ImportWALMessages
                 
.MESSAGE_REPLAYED_ARG_OPERATIONS_FROM_ARG_WAL_FILES_SKIPPED_ARG_ENTRIES_F0D37E3A,
@@ -156,6 +159,11 @@ public class ImportWAL {
             statistics.getTotalBytes(),
             statistics.getAverageRateMbPerSecond());
         out.println();
+        if (deleteSource) {
+          out.printf(
+              ImportWALMessages.MESSAGE_DELETED_ARG_SOURCE_WAL_FILES_C7A5AA1B, 
walFiles.size());
+          out.println();
+        }
       } finally {
         closeSession(tableSession);
         closeSession(treeSession);
@@ -211,6 +219,15 @@ public class ImportWAL {
             .hasArg()
             
.desc(ImportWALMessages.MESSAGE_TARGET_DATABASE_FOR_TABLE_MODEL_WAL_ENTRIES_27BACD1C)
             .build());
+    options.addOption(
+        Option.builder("os")
+            .longOpt("on_success")
+            .argName("on_success")
+            .hasArg()
+            .desc(
+                ImportWALMessages
+                    
.MESSAGE_WHEN_ALL_WAL_FILES_ARE_REPLAYED_SUCCESSFULLY_DO_OPERATION_ON_SOURCE_WAL_FILES_OPTIONAL_PARAMETERS_ARE_NONE_DEFAULT_AND_DELETE_41963A66)
+            .build());
     options.addOption(
         Option.builder()
             .longOpt("help")
@@ -242,6 +259,21 @@ public class ImportWAL {
     return new String(password);
   }
 
+  static boolean shouldDeleteSource(final String onSuccess) {
+    final String normalizedOnSuccess = onSuccess.trim();
+    if ("none".equalsIgnoreCase(normalizedOnSuccess)) {
+      return false;
+    }
+    if ("delete".equalsIgnoreCase(normalizedOnSuccess)) {
+      return true;
+    }
+    throw new IllegalArgumentException(
+        String.format(
+            ImportWALMessages
+                
.EXCEPTION_UNSUPPORTED_ON_SUCCESS_VALUE_ARG_EXPECTED_NONE_OR_DELETE_F1C8EACE,
+            onSuccess));
+  }
+
   private static boolean containsHelpOption(final String[] args) {
     if (args == null) {
       return false;
@@ -368,6 +400,15 @@ public class ImportWAL {
   static ReplayStatistics replayWALFiles(
       final List<Path> walFiles, final WALReplayer replayer, final PrintStream 
progressStream)
       throws IOException {
+    return replayWALFiles(walFiles, replayer, progressStream, false);
+  }
+
+  static ReplayStatistics replayWALFiles(
+      final List<Path> walFiles,
+      final WALReplayer replayer,
+      final PrintStream progressStream,
+      final boolean deleteSource)
+      throws IOException {
     final ReplayStatistics statistics = new ReplayStatistics();
     final long startNanos = System.nanoTime();
     for (final Path walFile : walFiles) {
@@ -436,9 +477,27 @@ public class ImportWAL {
             e);
       }
     }
+    if (deleteSource) {
+      deleteSourceWALFiles(walFiles);
+    }
     return statistics;
   }
 
+  private static void deleteSourceWALFiles(final List<Path> walFiles) throws 
IOException {
+    for (final Path walFile : walFiles) {
+      try {
+        Files.delete(walFile);
+      } catch (final IOException e) {
+        throw new IOException(
+            String.format(
+                
ImportWALMessages.EXCEPTION_FAILED_TO_DELETE_SOURCE_WAL_FILE_ARG_ARG_236AF580,
+                walFile,
+                e.getMessage()),
+            e);
+      }
+    }
+  }
+
   static class WALReplayer {
 
     private final Session treeSession;
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/tools/ImportWALTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/tools/ImportWALTest.java
index 787e06e1975..c8efb24ef9f 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/tools/ImportWALTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/tools/ImportWALTest.java
@@ -104,6 +104,26 @@ public class ImportWALTest {
         files);
   }
 
+  /** Covers CLI discovery of the source-file operation without opening a 
Session. */
+  @Test
+  public void testHelpDescribesDeleteSourceOption() {
+    final ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+    final int exitCode =
+        ImportWAL.run(new String[] {"--help"}, new PrintStream(output), new 
PrintStream(output));
+
+    assertEquals(0, exitCode);
+    assertTrue(output.toString().contains("--on_success"));
+  }
+
+  /** Covers the default retention value, deletion value, normalization, and 
invalid input. */
+  @Test
+  public void testParseOnSuccessOption() {
+    assertFalse(ImportWAL.shouldDeleteSource("none"));
+    assertTrue(ImportWAL.shouldDeleteSource(" DELETE "));
+    assertThrows(IllegalArgumentException.class, () -> 
ImportWAL.shouldDeleteSource("unsupported"));
+  }
+
   /**
    * Covers a real WAL file containing one tree insert and one internal 
signal. The insert must be
    * sent once as a Tablet, while the signal is counted as skipped and no 
corruption is reported.
@@ -126,6 +146,49 @@ public class ImportWALTest {
     verify(treeSession).insertTablet(tabletCaptor.capture());
     assertEquals("root.sg.d1", tabletCaptor.getValue().getDeviceId());
     assertEquals(100, tabletCaptor.getValue().getTimestamp(0));
+    assertTrue(walFile.exists());
+  }
+
+  /** Covers opt-in deletion after every source WAL file completes 
successfully. */
+  @Test
+  public void testReplayDeletesSourceFilesAfterAllFilesSucceed() throws 
Exception {
+    final File firstWALFile = createWALFile(0);
+    final File secondWALFile = createWALFile(1);
+    writeWAL(
+        firstWALFile, new WALInfoEntry(1, 
WALTestUtils.getInsertRowNode("root.sg.delete.d1", 1)));
+    writeWAL(
+        secondWALFile, new WALInfoEntry(2, 
WALTestUtils.getInsertRowNode("root.sg.delete.d2", 2)));
+
+    ImportWAL.replayWALFiles(
+        Arrays.asList(firstWALFile.toPath(), secondWALFile.toPath()),
+        new ImportWAL.WALReplayer(mock(Session.class), null, null),
+        null,
+        true);
+
+    assertFalse(firstWALFile.exists());
+    assertFalse(secondWALFile.exists());
+  }
+
+  /** Covers all-or-nothing replay gating: a later failure must retain every 
source WAL file. */
+  @Test
+  public void testReplayRetainsAllSourceFilesWhenAnyFileFails() throws 
Exception {
+    final File validWALFile = createWALFile(0);
+    final File corruptedWALFile = createWALFile(1);
+    writeWAL(
+        validWALFile, new WALInfoEntry(1, 
WALTestUtils.getInsertRowNode("root.sg.retain.d1", 1)));
+    Files.write(corruptedWALFile.toPath(), new byte[] 
{WALEntryType.INSERT_ROW_NODE.getCode()});
+
+    assertThrows(
+        IOException.class,
+        () ->
+            ImportWAL.replayWALFiles(
+                Arrays.asList(validWALFile.toPath(), 
corruptedWALFile.toPath()),
+                new ImportWAL.WALReplayer(mock(Session.class), null, null),
+                null,
+                true));
+
+    assertTrue(validWALFile.exists());
+    assertTrue(corruptedWALFile.exists());
   }
 
   @Test

Reply via email to