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

github-merge-queue[bot] pushed a commit to branch 
gh-readonly-queue/dev/pr-12942-a89de9b70419116b47be4ecb4e018f12a942bfc6
in repository https://gitbox.apache.org/repos/asf/seatunnel.git

commit 4ae9604aaf876c64890cce88cc202a755bc36a64
Author: Goutam Adwant <[email protected]>
AuthorDate: Mon Oct 26 14:45:05 2026 +0000

    [Fix][Connector-V2] Restore bounded file splits after reader registration 
(#12942)
    
    Signed-off-by: Goutam Adwant <[email protected]>
---
 docs/en/connectors/source/LocalFile.md             |   4 +
 docs/zh/connectors/source/LocalFile.md             |   4 +
 .../MultipleTableFileSourceSplitEnumerator.java    |  45 +++++--
 ...MultipleTableFileSourceSplitEnumeratorTest.java | 147 ++++++++++++++++++++
 .../e2e/connector/file/local/LocalFileIT.java      | 148 +++++++++++++++++++++
 .../resources/text/local_file_bounded_restore.conf |  44 ++++++
 6 files changed, 383 insertions(+), 9 deletions(-)

diff --git a/docs/en/connectors/source/LocalFile.md 
b/docs/en/connectors/source/LocalFile.md
index c169b133ec..7f98eb8618 100644
--- a/docs/en/connectors/source/LocalFile.md
+++ b/docs/en/connectors/source/LocalFile.md
@@ -49,6 +49,10 @@ If you use SeaTunnel Engine, It automatically integrated the 
hadoop jar when you
 
 :::
 
+## Checkpoint recovery
+
+For bounded Zeta jobs, restore resumes pending file splits after readers 
register and skips files already completed in the checkpoint. Keep input files 
unchanged and accessible at the same paths while restoring. Sink delivery 
guarantees still depend on the selected sink and its checkpoint support.
+
 ## Options
 
 | name                       | type    | required | default value              
          |
diff --git a/docs/zh/connectors/source/LocalFile.md 
b/docs/zh/connectors/source/LocalFile.md
index c82cf03359..bc69c073cb 100644
--- a/docs/zh/connectors/source/LocalFile.md
+++ b/docs/zh/connectors/source/LocalFile.md
@@ -49,6 +49,10 @@ import ChangeLog from '../changelog/connector-file-local.md';
 
 :::
 
+## 检查点恢复
+
+对于 Zeta 
批处理作业,恢复时会等待读取器注册后再分配未完成的文件分片,并跳过检查点中已经完成的文件。恢复期间,输入文件应保持不变,且仍可通过相同路径访问。最终写入语义仍取决于所选
 Sink 及其检查点支持。
+
 ## 选项
 
 | 名称                         | 类型      | 是否必须 | 默认值                 |
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/split/MultipleTableFileSourceSplitEnumerator.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/split/MultipleTableFileSourceSplitEnumerator.java
index 188714cf26..33b6e5f6f0 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/split/MultipleTableFileSourceSplitEnumerator.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/split/MultipleTableFileSourceSplitEnumerator.java
@@ -53,6 +53,8 @@ public class MultipleTableFileSourceSplitEnumerator
     private final List<BaseFileSourceConfig> fileSourceConfigs;
     private final AtomicInteger assignCount = new AtomicInteger(0);
     private final Object lock = new Object();
+    // Guarded by lock; set only in run() after reader registration and kept 
true for late returns.
+    private boolean running;
     private final FileSplitStrategy fileSplitStrategy;
     private final Set<String> documentRoutingTableIds;
 
@@ -150,25 +152,47 @@ public class MultipleTableFileSourceSplitEnumerator
         if (CollectionUtils.isEmpty(splits)) {
             return;
         }
-        allSplit.addAll(splits);
-        assignSplit(subtaskId);
+        synchronized (lock) {
+            allSplit.addAll(splits);
+            assignedSplit.removeAll(splits);
+            // Native readers restore their pending splits before registering. 
run() starts
+            // after registration; only a running enumerator can dispatch a 
late return.
+            if (running) {
+                assignPendingSplitsToRegisteredReaders();
+            }
+        }
     }
 
     @Override
     public int currentUnassignedSplitSize() {
-        return allSplit.size() - assignedSplit.size();
+        synchronized (lock) {
+            return (int) allSplit.stream().filter(split -> 
!assignedSplit.contains(split)).count();
+        }
     }
 
     @Override
     public void handleSplitRequest(int subtaskId) {}
 
     @Override
-    public void registerReader(int subtaskId) {}
+    public void registerReader(int subtaskId) {
+        synchronized (lock) {
+            if (running) {
+                assignPendingSplitsToRegisteredReaders();
+            }
+        }
+    }
+
+    // Caller must hold lock while reading registered readers and updating 
split assignments.
+    private void assignPendingSplitsToRegisteredReaders() {
+        for (int reader : context.registeredReaders()) {
+            assignSplit(reader);
+        }
+    }
 
     @Override
     public FileSourceState snapshotState(long checkpointId) {
         synchronized (lock) {
-            return new FileSourceState(assignedSplit);
+            return new FileSourceState(new HashSet<>(assignedSplit));
         }
     }
 
@@ -197,7 +221,9 @@ public class MultipleTableFileSourceSplitEnumerator
                 }
             }
         }
-        // assign splits
+        // Keep the full candidate order for round-robin ownership, then 
exclude files
+        // already assigned in the checkpoint. Reader-returned pending splits 
are unassigned.
+        currentTaskSplits.removeIf(assignedSplit::contains);
         context.assignSplit(taskId, currentTaskSplits);
         // save the state of assigned splits
         assignedSplit.addAll(currentTaskSplits);
@@ -249,9 +275,10 @@ public class MultipleTableFileSourceSplitEnumerator
 
     @Override
     public void run() throws Exception {
-        for (int i = 0; i < context.currentParallelism(); i++) {
-            log.info("Assigned splits to reader [{}]", i);
-            synchronized (lock) {
+        synchronized (lock) {
+            running = true;
+            for (int i = 0; i < context.currentParallelism(); i++) {
+                log.info("Assigned splits to reader [{}]", i);
                 assignSplit(i);
             }
         }
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/source/split/MultipleTableFileSourceSplitEnumeratorTest.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/source/split/MultipleTableFileSourceSplitEnumeratorTest.java
index 83729daf01..af0b9e6052 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/source/split/MultipleTableFileSourceSplitEnumeratorTest.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/source/split/MultipleTableFileSourceSplitEnumeratorTest.java
@@ -31,6 +31,7 @@ import 
org.apache.seatunnel.connectors.seatunnel.file.config.FileBaseSourceOptio
 import org.apache.seatunnel.connectors.seatunnel.file.config.HadoopConf;
 import 
org.apache.seatunnel.connectors.seatunnel.file.exception.FileConnectorException;
 import 
org.apache.seatunnel.connectors.seatunnel.file.source.FileSourceDocumentRouting;
+import 
org.apache.seatunnel.connectors.seatunnel.file.source.state.FileSourceState;
 
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FSDataInputStream;
@@ -52,8 +53,10 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.IntStream;
 
 class MultipleTableFileSourceSplitEnumeratorTest {
@@ -263,6 +266,150 @@ class MultipleTableFileSourceSplitEnumeratorTest {
         Assertions.assertEquals(2, enumerator.currentUnassignedSplitSize());
     }
 
+    @Test
+    void restoredSplitsWaitForReaderRegistration() throws Exception {
+        SourceSplitEnumerator.Context<FileSourceSplit> context =
+                Mockito.mock(SourceSplitEnumerator.Context.class);
+        Mockito.when(context.currentParallelism()).thenReturn(2);
+        Set<Integer> readers = new HashSet<>();
+        Mockito.when(context.registeredReaders()).thenAnswer(invocation -> new 
HashSet<>(readers));
+        List<FileSourceSplit> files = recoveryFiles();
+        MultipleTableFileSourceSplitEnumerator enumerator =
+                recoveryEnumerator(context, files, new HashSet<>(files));
+        enumerator.open();
+
+        // Native readers return checkpointed splits before registering with 
the enumerator.
+        enumerator.addSplitsBack(Collections.singletonList(files.get(1)), 0);
+        readers.add(0);
+        enumerator.registerReader(0);
+        enumerator.addSplitsBack(Collections.singletonList(files.get(2)), 1);
+        Mockito.verify(context, Mockito.never()).assignSplit(Mockito.anyInt(), 
Mockito.anyList());
+        Mockito.verify(context, 
Mockito.never()).signalNoMoreSplits(Mockito.anyInt());
+
+        readers.add(1);
+        enumerator.registerReader(1);
+        Assertions.assertEquals(2, enumerator.currentUnassignedSplitSize());
+        enumerator.run();
+        ArgumentCaptor<List> assignments = ArgumentCaptor.forClass(List.class);
+        Mockito.verify(context, Mockito.times(2))
+                .assignSplit(Mockito.anyInt(), assignments.capture());
+        List<FileSourceSplit> delivered = new ArrayList<>();
+        assignments.getAllValues().forEach(delivered::addAll);
+        Assertions.assertEquals(2, delivered.size());
+        Assertions.assertEquals(new HashSet<>(files.subList(1, 3)), new 
HashSet<>(delivered));
+        Assertions.assertEquals(0, enumerator.currentUnassignedSplitSize());
+    }
+
+    @Test
+    void restoreDoesNotReplayCheckpointCompletedFiles() throws Exception {
+        SourceSplitEnumerator.Context<FileSourceSplit> context =
+                Mockito.mock(SourceSplitEnumerator.Context.class);
+        Mockito.when(context.currentParallelism()).thenReturn(2);
+        List<FileSourceSplit> files = recoveryFiles();
+        MultipleTableFileSourceSplitEnumerator enumerator =
+                recoveryEnumerator(context, files, new HashSet<>(files));
+        enumerator.open();
+        Assertions.assertEquals(0, enumerator.currentUnassignedSplitSize());
+        enumerator.run();
+        Mockito.verify(context, Mockito.times(2))
+                .assignSplit(Mockito.anyInt(), 
Mockito.eq(Collections.emptyList()));
+        Mockito.verify(context).signalNoMoreSplits(0);
+        Mockito.verify(context).signalNoMoreSplits(1);
+    }
+
+    @Test
+    void checkpointStateDoesNotChangeAfterLaterAssignment() throws Exception {
+        SourceSplitEnumerator.Context<FileSourceSplit> context =
+                Mockito.mock(SourceSplitEnumerator.Context.class);
+        Mockito.when(context.currentParallelism()).thenReturn(1);
+        MultipleTableFileSourceSplitEnumerator enumerator =
+                recoveryEnumerator(context, recoveryFiles(), 
Collections.emptySet());
+        enumerator.open();
+        FileSourceState checkpoint = enumerator.snapshotState(1L);
+        enumerator.run();
+        Assertions.assertTrue(checkpoint.getAssignedSplit().isEmpty());
+        Assertions.assertEquals(3, 
enumerator.snapshotState(2L).getAssignedSplit().size());
+    }
+
+    @Test
+    void lateReturnedSplitsAreAssignedWithoutReplayingOtherFiles() throws 
Exception {
+        SourceSplitEnumerator.Context<FileSourceSplit> context =
+                Mockito.mock(SourceSplitEnumerator.Context.class);
+        Mockito.when(context.currentParallelism()).thenReturn(2);
+        Mockito.when(context.registeredReaders()).thenReturn(new 
HashSet<>(Arrays.asList(0, 1)));
+        List<FileSourceSplit> files = recoveryFiles();
+        MultipleTableFileSourceSplitEnumerator enumerator =
+                recoveryEnumerator(context, files, Collections.emptySet());
+        enumerator.open();
+        enumerator.run();
+        Mockito.clearInvocations(context);
+
+        // The reporting subtask need not be the split's route after 
redistribution.
+        enumerator.addSplitsBack(Collections.singletonList(files.get(1)), 0);
+        ArgumentCaptor<List> assignments = ArgumentCaptor.forClass(List.class);
+        Mockito.verify(context, Mockito.atLeastOnce())
+                .assignSplit(Mockito.anyInt(), assignments.capture());
+        List<FileSourceSplit> delivered = new ArrayList<>();
+        assignments.getAllValues().forEach(delivered::addAll);
+        Assertions.assertEquals(Collections.singletonList(files.get(1)), 
delivered);
+        Assertions.assertEquals(0, enumerator.currentUnassignedSplitSize());
+    }
+
+    @Test
+    void completedFilesMissingFromListingDoNotMakePendingCountNegative() 
throws Exception {
+        SourceSplitEnumerator.Context<FileSourceSplit> context =
+                Mockito.mock(SourceSplitEnumerator.Context.class);
+        Mockito.when(context.currentParallelism()).thenReturn(1);
+        List<FileSourceSplit> files = recoveryFiles();
+        MultipleTableFileSourceSplitEnumerator enumerator =
+                recoveryEnumerator(
+                        context, Collections.singletonList(files.get(0)), new 
HashSet<>(files));
+        enumerator.open();
+        Assertions.assertEquals(0, enumerator.currentUnassignedSplitSize());
+        enumerator.run();
+        Mockito.verify(context).assignSplit(0, Collections.emptyList());
+    }
+
+    @Test
+    void repeatedRunDoesNotAssignFilesAgain() throws Exception {
+        SourceSplitEnumerator.Context<FileSourceSplit> context =
+                Mockito.mock(SourceSplitEnumerator.Context.class);
+        Mockito.when(context.currentParallelism()).thenReturn(2);
+        MultipleTableFileSourceSplitEnumerator enumerator =
+                recoveryEnumerator(context, recoveryFiles(), 
Collections.emptySet());
+        enumerator.open();
+        enumerator.run();
+        Mockito.clearInvocations(context);
+        enumerator.run();
+        Mockito.verify(context, Mockito.times(2))
+                .assignSplit(Mockito.anyInt(), 
Mockito.eq(Collections.emptyList()));
+    }
+
+    private static List<FileSourceSplit> recoveryFiles() {
+        return Arrays.asList(
+                new FileSourceSplit("catalog.test.files", "0.txt"),
+                new FileSourceSplit("catalog.test.files", "1.txt"),
+                new FileSourceSplit("catalog.test.files", "2.txt"));
+    }
+
+    private static MultipleTableFileSourceSplitEnumerator recoveryEnumerator(
+            SourceSplitEnumerator.Context<FileSourceSplit> context,
+            List<FileSourceSplit> files,
+            Set<FileSourceSplit> checkpointAssigned) {
+        BaseFileSourceConfig file = Mockito.mock(BaseFileSourceConfig.class);
+        CatalogTable table = CatalogTableUtil.buildSimpleTextTable();
+        Mockito.when(file.getCatalogTable()).thenReturn(table);
+        Mockito.when(file.getFilePathsForSplitEnumerator())
+                .thenReturn(Collections.singletonList("input"));
+        BaseMultipleTableFileSourceConfig config =
+                Mockito.mock(BaseMultipleTableFileSourceConfig.class);
+        
Mockito.when(config.getFileSourceConfigs()).thenReturn(Collections.singletonList(file));
+        FileSplitStrategy strategy = Mockito.mock(FileSplitStrategy.class);
+        Mockito.when(strategy.split(Mockito.anyString(), 
Mockito.eq("input"))).thenReturn(files);
+        return new MultipleTableFileSourceSplitEnumerator(
+                context, config, strategy, new 
FileSourceState(checkpointAssigned));
+    }
+
     private int allocateFiles(int id, int parallelism, int fileSize) {
         int filesPerIteration = fileSize / parallelism;
         int remainder = fileSize % parallelism;
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/java/org/apache/seatunnel/e2e/connector/file/local/LocalFileIT.java
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/java/org/apache/seatunnel/e2e/connector/file/local/LocalFileIT.java
index eb135349f2..34ead22e85 100644
--- 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/java/org/apache/seatunnel/e2e/connector/file/local/LocalFileIT.java
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/java/org/apache/seatunnel/e2e/connector/file/local/LocalFileIT.java
@@ -909,6 +909,154 @@ public class LocalFileIT extends TestSuiteBase {
         Assertions.assertFalse(localFileCatalog.tableExists(tablePath));
     }
 
+    @TestTemplate
+    @DisabledOnContainer(
+            value = {},
+            type = {EngineType.SPARK, EngineType.FLINK},
+            disabledReason = "Savepoint restore uses the native Zeta reader 
lifecycle")
+    public void testBoundedFileSavepointRestore(TestContainer container) 
throws Exception {
+        String jobId = String.valueOf(JobIdGenerator.newJobId());
+        String config = "/text/local_file_bounded_restore.conf";
+        String root = "/tmp/seatunnel_mnt/bounded-restore";
+        Container.ExecResult setup =
+                baseContainer.execInContainer(
+                        "sh",
+                        "-c",
+                        "rm -rf "
+                                + root
+                                + " && mkdir -p "
+                                + root
+                                + "/source "
+                                + root
+                                + "/output "
+                                + root
+                                + "/pending && i=1; while [ $i -le 120 ]; do 
echo $i > "
+                                + root
+                                + "/source/$i.txt; i=$((i+1)); done");
+        Assertions.assertEquals(0, setup.getExitCode(), setup.getStderr());
+        CompletableFuture<Container.ExecResult> firstRun =
+                CompletableFuture.supplyAsync(
+                        () -> {
+                            try {
+                                return container.executeJob(config, jobId);
+                            } catch (Exception e) {
+                                throw new RuntimeException(e);
+                            }
+                        });
+        List<CompletableFuture<Container.ExecResult>> runs = new ArrayList<>();
+        runs.add(firstRun);
+        Throwable failure = null;
+        try {
+            Awaitility.await()
+                    .atMost(120, TimeUnit.SECONDS)
+                    .failFast(
+                            "Bounded job ended before checkpointed partial 
output",
+                            firstRun::isDone)
+                    .untilAsserted(
+                            () -> {
+                                Assertions.assertTrue(
+                                        
container.getCompletedCheckpointCount(jobId) > 0);
+                                int committed = readBoundedRestoreIds().size();
+                                Assertions.assertTrue(
+                                        committed > 0 && committed < 120,
+                                        "Checkpoint must commit a proper 
subset before restore");
+                            });
+            Container.ExecResult savepoint = container.savepointJob(jobId);
+            Assertions.assertEquals(0, savepoint.getExitCode(), 
savepoint.getStderr());
+            Container.ExecResult stopped = firstRun.get(120, TimeUnit.SECONDS);
+            Assertions.assertEquals(0, stopped.getExitCode(), 
stopped.getStderr());
+            Assertions.assertTrue(
+                    readBoundedRestoreIds().size() < 120,
+                    "The saved job must still have unfinished file splits");
+            CompletableFuture<Container.ExecResult> restored =
+                    CompletableFuture.supplyAsync(
+                            () -> {
+                                try {
+                                    return container.restoreJob(config, jobId);
+                                } catch (Exception e) {
+                                    throw new RuntimeException(e);
+                                }
+                            });
+            runs.add(restored);
+            Container.ExecResult completed = restored.get(180, 
TimeUnit.SECONDS);
+            Assertions.assertEquals(0, completed.getExitCode(), 
completed.getStderr());
+            List<Integer> expected = new ArrayList<>();
+            for (int id = 1; id <= 120; id++) {
+                expected.add(id);
+            }
+            List<Integer> actual = readBoundedRestoreIds();
+            actual.sort(Integer::compareTo);
+            Assertions.assertEquals(
+                    expected,
+                    actual,
+                    "Restore must neither replay completed files nor lose 
pending splits");
+        } catch (Exception | AssertionError e) {
+            failure = e;
+            throw e;
+        } finally {
+            try {
+                List<String> terminal =
+                        Lists.newArrayList("FINISHED", "FAILED", "CANCELED", 
"SAVEPOINT_DONE");
+                String status = container.getJobStatus(jobId);
+                if (status == null || "UNKNOWABLE".equals(status)) {
+                    throw new IllegalStateException(
+                            "Cannot confirm native termination; preserve 
fixture files");
+                }
+                if (!terminal.contains(status)) {
+                    CompletableFuture<Container.ExecResult> cancellation =
+                            CompletableFuture.supplyAsync(
+                                    () -> {
+                                        try {
+                                            return container.cancelJob(jobId);
+                                        } catch (Exception e) {
+                                            throw new RuntimeException(e);
+                                        }
+                                    });
+                    Container.ExecResult cancel = cancellation.get(30, 
TimeUnit.SECONDS);
+                    Assertions.assertEquals(0, cancel.getExitCode(), 
cancel.getStderr());
+                }
+                Awaitility.await()
+                        .atMost(120, TimeUnit.SECONDS)
+                        .untilAsserted(
+                                () ->
+                                        Assertions.assertTrue(
+                                                
terminal.contains(container.getJobStatus(jobId)),
+                                                "Keep fixture files until the 
native job is confirmed terminal"));
+                for (CompletableFuture<Container.ExecResult> run : runs) {
+                    if (!run.isDone()) {
+                        run.get(120, TimeUnit.SECONDS);
+                    }
+                }
+                Container.ExecResult cleanup =
+                        baseContainer.execInContainer("sh", "-c", "rm -rf " + 
root);
+                Assertions.assertEquals(0, cleanup.getExitCode(), 
cleanup.getStderr());
+            } catch (Exception | AssertionError cleanupFailure) {
+                if (failure != null) {
+                    failure.addSuppressed(cleanupFailure);
+                } else {
+                    throw cleanupFailure;
+                }
+            }
+        }
+    }
+
+    private List<Integer> readBoundedRestoreIds() throws IOException, 
InterruptedException {
+        Container.ExecResult result =
+                baseContainer.execInContainer(
+                        "sh",
+                        "-c",
+                        "find /tmp/seatunnel_mnt/bounded-restore/output -type 
f "
+                                + "! -name '.*' -exec awk '1' {} +");
+        Assertions.assertEquals(0, result.getExitCode(), result.getStderr());
+        List<Integer> ids = new ArrayList<>();
+        for (String line : result.getStdout().split("\\R")) {
+            if (!line.trim().isEmpty()) {
+                ids.add(Integer.parseInt(line.trim()));
+            }
+        }
+        return ids;
+    }
+
     private void resetUpdateTestPath() throws IOException, 
InterruptedException {
         Container.ExecResult result =
                 baseContainer.execInContainer(
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/resources/text/local_file_bounded_restore.conf
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/resources/text/local_file_bounded_restore.conf
new file mode 100644
index 0000000000..df19ce2d34
--- /dev/null
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/resources/text/local_file_bounded_restore.conf
@@ -0,0 +1,44 @@
+#
+# 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.
+#
+
+env {
+  parallelism = 2
+  job.mode = "BATCH"
+  checkpoint.interval = 1000
+  read_limit.rows_per_second = 2
+}
+source {
+  LocalFile {
+    path = "/tmp/seatunnel_mnt/bounded-restore/source"
+    file_format_type = "text"
+    schema {
+      fields {
+        id = int
+      }
+    }
+  }
+}
+sink {
+  LocalFile {
+    path = "/tmp/seatunnel_mnt/bounded-restore/output"
+    tmp_path = "/tmp/seatunnel_mnt/bounded-restore/pending"
+    file_format_type = "text"
+    is_enable_transaction = true
+    file_name_expression = "${transactionId}"
+    row_delimiter = "\n"
+  }
+}

Reply via email to