This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new ca3c7d3b0b [core][flink] Support long limits in the read path (#10028)
ca3c7d3b0b is described below
commit ca3c7d3b0b07a8ded664349e0f3012fe6b1eaa12
Author: Akash Reddy Jammula <[email protected]>
AuthorDate: Sun Sep 20 19:38:51 2026 -0700
[core][flink] Support long limits in the read path (#10028)
---
.../apache/paimon/reader/LimitRecordReader.java | 2 +-
.../paimon/reader/LimitRecordReaderTest.java | 20 +++++-
.../paimon/globalindex/DataEvolutionBatchScan.java | 4 +-
.../apache/paimon/operation/RawFileSplitRead.java | 11 ++-
.../org/apache/paimon/operation/SplitRead.java | 6 +-
.../paimon/table/FallbackReadFileStoreTable.java | 2 +-
.../paimon/table/format/FormatReadBuilder.java | 19 ++++-
.../paimon/table/format/FormatTableRead.java | 4 +-
.../paimon/table/format/FormatTableScan.java | 6 +-
.../table/source/AbstractBatchTableScan.java | 9 +--
.../paimon/table/source/AppendTableRead.java | 4 +-
.../table/source/BlobViewTableReadSupport.java | 2 +-
.../apache/paimon/table/source/InnerTableRead.java | 2 +-
.../apache/paimon/table/source/InnerTableScan.java | 2 +-
.../paimon/table/source/KeyValueTableRead.java | 4 +-
.../apache/paimon/table/source/ReadBuilder.java | 13 ++++
.../paimon/table/source/ReadBuilderImpl.java | 24 ++++++-
.../table/source/snapshot/SnapshotReader.java | 2 +-
.../table/source/snapshot/SnapshotReaderImpl.java | 2 +-
.../apache/paimon/table/system/AuditLogTable.java | 4 +-
.../paimon/table/format/FormatReadBuilderTest.java | 7 ++
.../paimon/table/source/ReadBuilderImplTest.java | 83 ++++++++++++++++++++++
.../paimon/flink/source/FlinkSourceBuilder.java | 2 +-
.../flink/source/FlinkSourceBuilderTest.java | 26 +++++++
24 files changed, 224 insertions(+), 36 deletions(-)
diff --git
a/paimon-common/src/main/java/org/apache/paimon/reader/LimitRecordReader.java
b/paimon-common/src/main/java/org/apache/paimon/reader/LimitRecordReader.java
index 2f0766e29a..836ae12b1e 100644
---
a/paimon-common/src/main/java/org/apache/paimon/reader/LimitRecordReader.java
+++
b/paimon-common/src/main/java/org/apache/paimon/reader/LimitRecordReader.java
@@ -40,7 +40,7 @@ public final class LimitRecordReader<T> implements
RecordReader<T> {
this.limit = limit;
}
- public static <T> RecordReader<T> limit(RecordReader<T> reader, @Nullable
Integer limit) {
+ public static <T> RecordReader<T> limit(RecordReader<T> reader, @Nullable
Long limit) {
if (limit == null || limit <= 0) {
return reader;
}
diff --git
a/paimon-common/src/test/java/org/apache/paimon/reader/LimitRecordReaderTest.java
b/paimon-common/src/test/java/org/apache/paimon/reader/LimitRecordReaderTest.java
index 6e5224d98d..53571c62cb 100644
---
a/paimon-common/src/test/java/org/apache/paimon/reader/LimitRecordReaderTest.java
+++
b/paimon-common/src/test/java/org/apache/paimon/reader/LimitRecordReaderTest.java
@@ -25,10 +25,28 @@ import org.junit.jupiter.api.Test;
import javax.annotation.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
/** Tests for {@link LimitRecordReader}. */
public class LimitRecordReaderTest {
+ @Test
+ public void testLimitAboveIntegerRangeDoesNotNarrow() throws Exception {
+ RecordReader<Integer> input = mock(RecordReader.class);
+ RecordReader.RecordIterator<Integer> inputBatch =
mock(RecordReader.RecordIterator.class);
+ when(input.readBatch()).thenReturn(inputBatch, null);
+ when(inputBatch.next()).thenReturn(0, 1, 2, null);
+
+ try (RecordReader<Integer> reader = LimitRecordReader.limit(input,
4294967297L)) {
+ RecordReader.RecordIterator<Integer> batch = reader.readBatch();
+ assertThat(batch.next()).isEqualTo(0);
+ assertThat(batch.next()).isEqualTo(1);
+ assertThat(batch.next()).isEqualTo(2);
+ assertThat(batch.next()).isNull();
+ }
+ }
+
@Test
public void testPreservesFileRecordIterator() throws Exception {
FileRecordIterator<Integer> fileIterator =
@@ -74,7 +92,7 @@ public class LimitRecordReaderTest {
public void close() {}
};
- try (RecordReader<Integer> reader =
LimitRecordReader.limit(fileReader, 2)) {
+ try (RecordReader<Integer> reader =
LimitRecordReader.limit(fileReader, 2L)) {
RecordReader.RecordIterator<Integer> batch = reader.readBatch();
assertThat(batch).isInstanceOf(FileRecordIterator.class);
diff --git
a/paimon-core/src/main/java/org/apache/paimon/globalindex/DataEvolutionBatchScan.java
b/paimon-core/src/main/java/org/apache/paimon/globalindex/DataEvolutionBatchScan.java
index 1a408c87fa..ed3bf2f623 100644
---
a/paimon-core/src/main/java/org/apache/paimon/globalindex/DataEvolutionBatchScan.java
+++
b/paimon-core/src/main/java/org/apache/paimon/globalindex/DataEvolutionBatchScan.java
@@ -70,7 +70,7 @@ public class DataEvolutionBatchScan implements DataTableScan {
private Predicate filter;
private TopN topN;
- private Integer pushDownLimit;
+ private Long pushDownLimit;
// set when part of the filter reaches the reader only, so limit/TopN must
not prune ahead of it
private boolean rowIdFilterDeferred;
private RowRangeIndex pushedRowRangeIndex;
@@ -184,7 +184,7 @@ public class DataEvolutionBatchScan implements
DataTableScan {
}
@Override
- public InnerTableScan withLimit(int limit) {
+ public InnerTableScan withLimit(long limit) {
// forwarded in plan(), once withFilter has said whether a row-id part
was deferred
this.pushDownLimit = limit;
return this;
diff --git
a/paimon-core/src/main/java/org/apache/paimon/operation/RawFileSplitRead.java
b/paimon-core/src/main/java/org/apache/paimon/operation/RawFileSplitRead.java
index 0c79a11db2..84b32eaa1d 100644
---
a/paimon-core/src/main/java/org/apache/paimon/operation/RawFileSplitRead.java
+++
b/paimon-core/src/main/java/org/apache/paimon/operation/RawFileSplitRead.java
@@ -92,7 +92,7 @@ public class RawFileSplitRead implements
SplitRead<InternalRow> {
private RowType readRowType;
@Nullable private List<Predicate> filters;
@Nullable private TopN topN;
- @Nullable private Integer limit;
+ @Nullable private Long limit;
@Nullable private ReadBatchSizer readBatchSizer;
public RawFileSplitRead(
@@ -151,7 +151,7 @@ public class RawFileSplitRead implements
SplitRead<InternalRow> {
}
@Override
- public SplitRead<InternalRow> withLimit(@Nullable Integer limit) {
+ public SplitRead<InternalRow> withLimit(@Nullable Long limit) {
this.limit = limit;
return this;
}
@@ -210,7 +210,7 @@ public class RawFileSplitRead implements
SplitRead<InternalRow> {
RowType outputRowType = readRowType;
Builder formatReaderMappingBuilder =
- createFormatReaderMappingBuilder(outputRowType, topN, limit);
+ createFormatReaderMappingBuilder(outputRowType, topN,
intLimit(limit));
for (DataFileMeta file : files) {
suppliers.add(
@@ -277,6 +277,11 @@ public class RawFileSplitRead implements
SplitRead<InternalRow> {
nestedFieldEnabled);
}
+ @Nullable
+ private static Integer intLimit(@Nullable Long limit) {
+ return limit != null && limit <= Integer.MAX_VALUE ? limit.intValue()
: null;
+ }
+
private ReaderSupplier<InternalRow> createFileReader(
BinaryRow partition,
DataFilePathFactory dataFilePathFactory,
diff --git
a/paimon-core/src/main/java/org/apache/paimon/operation/SplitRead.java
b/paimon-core/src/main/java/org/apache/paimon/operation/SplitRead.java
index 6a1ed5fc00..22fa440169 100644
--- a/paimon-core/src/main/java/org/apache/paimon/operation/SplitRead.java
+++ b/paimon-core/src/main/java/org/apache/paimon/operation/SplitRead.java
@@ -50,10 +50,14 @@ public interface SplitRead<T> {
return this;
}
- default SplitRead<T> withLimit(@Nullable Integer limit) {
+ default SplitRead<T> withLimit(@Nullable Long limit) {
return this;
}
+ default SplitRead<T> withLimit(int limit) {
+ return withLimit(Long.valueOf(limit));
+ }
+
default SplitRead<T> withReadBatchSizer(ReadBatchSizer sizer) {
return this;
}
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/FallbackReadFileStoreTable.java
b/paimon-core/src/main/java/org/apache/paimon/table/FallbackReadFileStoreTable.java
index cbdf01fd42..2863320050 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/FallbackReadFileStoreTable.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/FallbackReadFileStoreTable.java
@@ -468,7 +468,7 @@ public class FallbackReadFileStoreTable extends
DelegatedFileStoreTable {
}
@Override
- public FallbackReadScan withLimit(int limit) {
+ public FallbackReadScan withLimit(long limit) {
mainScan.withLimit(limit);
fallbackScan.withLimit(limit);
return this;
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/format/FormatReadBuilder.java
b/paimon-core/src/main/java/org/apache/paimon/table/format/FormatReadBuilder.java
index 97762e225c..1bdc1dbb6b 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/format/FormatReadBuilder.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/format/FormatReadBuilder.java
@@ -78,7 +78,9 @@ public class FormatReadBuilder implements ReadBuilder {
private final CoreOptions options;
@Nullable private Predicate filter;
@Nullable private PartitionPredicate partitionFilter;
+ // Keep the legacy field for Java serialization compatibility.
@Nullable private Integer limit;
+ @Nullable private Long longLimit;
@Nullable private transient FormatTableFileIOResolver fileIOResolver;
public FormatReadBuilder(FormatTable table) {
@@ -161,6 +163,14 @@ public class FormatReadBuilder implements ReadBuilder {
@Override
public ReadBuilder withLimit(int limit) {
this.limit = limit;
+ this.longLimit = (long) limit;
+ return this;
+ }
+
+ @Override
+ public ReadBuilder withLimit(long limit) {
+ this.longLimit = limit;
+ this.limit = limit >= Integer.MIN_VALUE && limit <= Integer.MAX_VALUE
? (int) limit : null;
return this;
}
@@ -176,12 +186,17 @@ public class FormatReadBuilder implements ReadBuilder {
partitionFilter = partitionPredicateOpt.get();
}
}
- return new FormatTableScan(table, partitionFilter, limit);
+ return new FormatTableScan(table, partitionFilter, effectiveLimit());
}
@Override
public TableRead newRead() {
- return new FormatTableRead(readType(), table.rowType(), this, filter,
limit);
+ return new FormatTableRead(readType(), table.rowType(), this, filter,
effectiveLimit());
+ }
+
+ @Nullable
+ private Long effectiveLimit() {
+ return longLimit != null ? longLimit : limit == null ? null :
limit.longValue();
}
protected RecordReader<InternalRow> createReader(FormatDataSplit
dataSplit) throws IOException {
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/format/FormatTableRead.java
b/paimon-core/src/main/java/org/apache/paimon/table/format/FormatTableRead.java
index 51f3d2b1e4..d3a2eb23e9 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/format/FormatTableRead.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/format/FormatTableRead.java
@@ -43,7 +43,7 @@ public class FormatTableRead implements TableRead {
private final RowType tableRowType;
private final Predicate predicate;
private final FormatReadBuilder read;
- private final Integer limit;
+ private final Long limit;
private boolean executeFilter = false;
@Nullable private ReadBatchSizer readBatchSizer;
@@ -53,7 +53,7 @@ public class FormatTableRead implements TableRead {
RowType tableRowType,
FormatReadBuilder read,
Predicate predicate,
- Integer limit) {
+ Long limit) {
this.tableRowType = tableRowType;
this.readType = readType == null ? tableRowType : readType;
this.read = read;
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/format/FormatTableScan.java
b/paimon-core/src/main/java/org/apache/paimon/table/format/FormatTableScan.java
index 32cd464315..4ad70d2509 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/format/FormatTableScan.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/format/FormatTableScan.java
@@ -78,12 +78,10 @@ public class FormatTableScan implements InnerTableScan {
final CoreOptions coreOptions;
@Nullable private PartitionPredicate partitionFilter;
private final SplitEnumerator splitEnumerator;
- @Nullable private final Integer limit;
+ @Nullable private final Long limit;
public FormatTableScan(
- FormatTable table,
- @Nullable PartitionPredicate partitionFilter,
- @Nullable Integer limit) {
+ FormatTable table, @Nullable PartitionPredicate partitionFilter,
@Nullable Long limit) {
this.table = table;
this.coreOptions = new CoreOptions(table.options());
this.partitionFilter = partitionFilter;
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/source/AbstractBatchTableScan.java
b/paimon-core/src/main/java/org/apache/paimon/table/source/AbstractBatchTableScan.java
index c95fb56a36..03a154ce86 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/source/AbstractBatchTableScan.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/source/AbstractBatchTableScan.java
@@ -56,7 +56,7 @@ public abstract class AbstractBatchTableScan extends
AbstractDataTableScan {
private StartingScanner startingScanner;
private boolean hasNext;
- private Integer pushDownLimit;
+ private Long pushDownLimit;
private TopN topN;
@Nullable private String readProtectionTagName;
@@ -97,7 +97,7 @@ public abstract class AbstractBatchTableScan extends
AbstractDataTableScan {
}
@Override
- public InnerTableScan withLimit(int limit) {
+ public InnerTableScan withLimit(long limit) {
// Record it; applyPushDownLimit pushes the file-store limit only when
safe.
this.pushDownLimit = limit;
return this;
@@ -196,8 +196,8 @@ public abstract class AbstractBatchTableScan extends
AbstractDataTableScan {
OptionalLong mergedRowCount = split.mergedRowCount();
if (mergedRowCount.isPresent()) {
limitedSplits.add(split);
- scannedRowCount += mergedRowCount.getAsLong();
- if (scannedRowCount >= pushDownLimit) {
+ long splitRowCount = mergedRowCount.getAsLong();
+ if (scannedRowCount >= pushDownLimit - splitRowCount) {
SnapshotReader.Plan newPlan =
new PlanImpl(plan.watermark(), plan.snapshotId(),
limitedSplits);
LOG.info(
@@ -207,6 +207,7 @@ public abstract class AbstractBatchTableScan extends
AbstractDataTableScan {
pushDownLimit);
return Optional.of(new ScannedResult(newPlan));
}
+ scannedRowCount += splitRowCount;
}
}
return Optional.of(result);
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/source/AppendTableRead.java
b/paimon-core/src/main/java/org/apache/paimon/table/source/AppendTableRead.java
index 99561b7551..ac5f9e3741 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/source/AppendTableRead.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/source/AppendTableRead.java
@@ -48,7 +48,7 @@ public class AppendTableRead extends AbstractDataTableRead {
@Nullable private RowType readType = null;
private Predicate predicate = null;
protected TopN topN = null;
- protected Integer limit = null;
+ protected Long limit = null;
@Nullable private ReadBatchSizer readBatchSizer;
public AppendTableRead(
@@ -104,7 +104,7 @@ public class AppendTableRead extends AbstractDataTableRead {
}
@Override
- public InnerTableRead withLimit(int limit) {
+ public InnerTableRead withLimit(long limit) {
initialized().forEach(r -> r.withLimit(limit));
this.limit = limit;
return this;
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/source/BlobViewTableReadSupport.java
b/paimon-core/src/main/java/org/apache/paimon/table/source/BlobViewTableReadSupport.java
index 6602d40a6e..5371ec8092 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/source/BlobViewTableReadSupport.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/source/BlobViewTableReadSupport.java
@@ -74,7 +74,7 @@ final class BlobViewTableReadSupport {
RowType readType,
@Nullable Predicate predicate,
@Nullable TopN topN,
- @Nullable Integer limit,
+ @Nullable Long limit,
boolean executeFilter,
RecordReaderSupplier dataReaderSupplier,
Supplier<InnerTableRead> prescanReadSupplier)
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/source/InnerTableRead.java
b/paimon-core/src/main/java/org/apache/paimon/table/source/InnerTableRead.java
index 0a325e70e9..4f005fa4e8 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/source/InnerTableRead.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/source/InnerTableRead.java
@@ -56,7 +56,7 @@ public interface InnerTableRead extends TableRead {
return this;
}
- default InnerTableRead withLimit(int limit) {
+ default InnerTableRead withLimit(long limit) {
return this;
}
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/source/InnerTableScan.java
b/paimon-core/src/main/java/org/apache/paimon/table/source/InnerTableScan.java
index 38278d4620..6c4b18ec9b 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/source/InnerTableScan.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/source/InnerTableScan.java
@@ -43,7 +43,7 @@ public interface InnerTableScan extends TableScan {
return this;
}
- default InnerTableScan withLimit(int limit) {
+ default InnerTableScan withLimit(long limit) {
return this;
}
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/source/KeyValueTableRead.java
b/paimon-core/src/main/java/org/apache/paimon/table/source/KeyValueTableRead.java
index 117798d562..167e287faa 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/source/KeyValueTableRead.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/source/KeyValueTableRead.java
@@ -68,7 +68,7 @@ public final class KeyValueTableRead extends
AbstractDataTableRead {
private Predicate predicate = null;
private IOManager ioManager = null;
@Nullable private TopN topN = null;
- @Nullable private Integer limit = null;
+ @Nullable private Long limit = null;
@Nullable private ReadBatchSizer readBatchSizer;
public KeyValueTableRead(
@@ -146,7 +146,7 @@ public final class KeyValueTableRead extends
AbstractDataTableRead {
}
@Override
- public InnerTableRead withLimit(int limit) {
+ public InnerTableRead withLimit(long limit) {
this.limit = limit;
return this;
}
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/source/ReadBuilder.java
b/paimon-core/src/main/java/org/apache/paimon/table/source/ReadBuilder.java
index ee94211a7f..a83dd59de9 100644
--- a/paimon-core/src/main/java/org/apache/paimon/table/source/ReadBuilder.java
+++ b/paimon-core/src/main/java/org/apache/paimon/table/source/ReadBuilder.java
@@ -135,6 +135,19 @@ public interface ReadBuilder extends Serializable {
/** the row number pushed down. */
ReadBuilder withLimit(int limit);
+ /**
+ * The row number pushed down.
+ *
+ * <p>The default implementation preserves compatibility with
implementations which only support
+ * integer limits. A limit outside the integer range is left unpushed
instead of being narrowed.
+ */
+ default ReadBuilder withLimit(long limit) {
+ if (limit < Integer.MIN_VALUE || limit > Integer.MAX_VALUE) {
+ return this;
+ }
+ return withLimit(Math.toIntExact(limit));
+ }
+
/**
* Push TopN filter. Will filter the data as much as possible, but it is
not guaranteed that it
* is a complete filter.
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/source/ReadBuilderImpl.java
b/paimon-core/src/main/java/org/apache/paimon/table/source/ReadBuilderImpl.java
index ee6949eddb..51c76d7183 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/source/ReadBuilderImpl.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/source/ReadBuilderImpl.java
@@ -58,7 +58,9 @@ public class ReadBuilderImpl implements ReadBuilder {
private Predicate filter;
+ // Keep the legacy field for Java serialization compatibility.
private Integer limit = null;
+ private Long longLimit = null;
private TopN topN = null;
private Integer shardIndexOfThisSubtask;
@@ -137,6 +139,14 @@ public class ReadBuilderImpl implements ReadBuilder {
@Override
public ReadBuilder withLimit(int limit) {
this.limit = limit;
+ this.longLimit = (long) limit;
+ return this;
+ }
+
+ @Override
+ public ReadBuilder withLimit(long limit) {
+ this.longLimit = limit;
+ this.limit = limit >= Integer.MIN_VALUE && limit <= Integer.MAX_VALUE
? (int) limit : null;
return this;
}
@@ -189,6 +199,7 @@ public class ReadBuilderImpl implements ReadBuilder {
@Override
public TableScan newScan() {
+ Long limit = effectiveLimit();
InnerTableScan tableScan = configureScan(table.newScan());
if (limit != null) {
tableScan.withLimit(limit);
@@ -240,6 +251,7 @@ public class ReadBuilderImpl implements ReadBuilder {
@Override
public TableRead newRead() {
+ Long limit = effectiveLimit();
InnerTableRead read = table.newRead().withFilter(filter);
if (readType != null) {
read.withReadType(readType);
@@ -252,6 +264,7 @@ public class ReadBuilderImpl implements ReadBuilder {
}
return read;
}
+
if (topN != null) {
read.withTopN(topN);
}
@@ -261,6 +274,11 @@ public class ReadBuilderImpl implements ReadBuilder {
return read;
}
+ @Nullable
+ private Long effectiveLimit() {
+ return longLimit != null ? longLimit : limit == null ? null :
limit.longValue();
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -290,13 +308,13 @@ public class ReadBuilderImpl implements ReadBuilder {
private static class LimitTableRead implements TableRead {
private final TableRead delegate;
- private final int limit;
+ private final long limit;
// with a filter the reader only evaluates it once executeFilter() is
requested;
// otherwise the engine does, after this limit, so capping here would
drop matches
private final boolean filterPresent;
private boolean filterExecutedByReader = false;
- private LimitTableRead(TableRead delegate, int limit, boolean
filterPresent) {
+ private LimitTableRead(TableRead delegate, long limit, boolean
filterPresent) {
this.delegate = delegate;
this.limit = limit;
this.filterPresent = filterPresent;
@@ -350,7 +368,7 @@ public class ReadBuilderImpl implements ReadBuilder {
// Stop reading once the limit is reached (return EOF), rather
than filtering and
// draining the rest of the data.
return new RecordReader<InternalRow>() {
- private int count;
+ private long count;
@Nullable
@Override
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/source/snapshot/SnapshotReader.java
b/paimon-core/src/main/java/org/apache/paimon/table/source/snapshot/SnapshotReader.java
index 7bc7150556..e4e252c7ab 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/source/snapshot/SnapshotReader.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/source/snapshot/SnapshotReader.java
@@ -132,7 +132,7 @@ public interface SnapshotReader {
SnapshotReader withReadType(RowType readType);
- SnapshotReader withLimit(int limit);
+ SnapshotReader withLimit(long limit);
/** Whether the pushed filter still contains non-partition predicates. */
boolean hasNonPartitionFilter();
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/source/snapshot/SnapshotReaderImpl.java
b/paimon-core/src/main/java/org/apache/paimon/table/source/snapshot/SnapshotReaderImpl.java
index cb338c8570..095959ddd3 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/source/snapshot/SnapshotReaderImpl.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/source/snapshot/SnapshotReaderImpl.java
@@ -353,7 +353,7 @@ public class SnapshotReaderImpl implements SnapshotReader {
}
@Override
- public SnapshotReader withLimit(int limit) {
+ public SnapshotReader withLimit(long limit) {
scan.withLimit(limit);
return this;
}
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/system/AuditLogTable.java
b/paimon-core/src/main/java/org/apache/paimon/table/system/AuditLogTable.java
index 8f259502ff..5ccc84886e 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/system/AuditLogTable.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/system/AuditLogTable.java
@@ -515,7 +515,7 @@ public class AuditLogTable implements DataTable,
ReadonlyTable {
}
@Override
- public SnapshotReader withLimit(int limit) {
+ public SnapshotReader withLimit(long limit) {
wrapped.withLimit(limit);
return this;
}
@@ -582,7 +582,7 @@ public class AuditLogTable implements DataTable,
ReadonlyTable {
}
@Override
- public InnerTableScan withLimit(int limit) {
+ public InnerTableScan withLimit(long limit) {
batchScan.withLimit(limit);
return this;
}
diff --git
a/paimon-core/src/test/java/org/apache/paimon/table/format/FormatReadBuilderTest.java
b/paimon-core/src/test/java/org/apache/paimon/table/format/FormatReadBuilderTest.java
index 7b3f7b0eff..3e48b5e9b2 100644
---
a/paimon-core/src/test/java/org/apache/paimon/table/format/FormatReadBuilderTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/table/format/FormatReadBuilderTest.java
@@ -53,6 +53,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
+import java.io.ObjectStreamClass;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
@@ -69,6 +70,12 @@ public class FormatReadBuilderTest {
@TempDir java.nio.file.Path tempPath;
+ @Test
+ public void testSerializedLimitFieldCompatibility() {
+
assertThat(ObjectStreamClass.lookup(FormatReadBuilder.class).getField("limit").getType())
+ .isEqualTo(Integer.class);
+ }
+
@Test
public void testSerializeAndDeserialize() throws IOException,
ClassNotFoundException {
RowType rowType =
diff --git
a/paimon-core/src/test/java/org/apache/paimon/table/source/ReadBuilderImplTest.java
b/paimon-core/src/test/java/org/apache/paimon/table/source/ReadBuilderImplTest.java
new file mode 100644
index 0000000000..c080905f03
--- /dev/null
+++
b/paimon-core/src/test/java/org/apache/paimon/table/source/ReadBuilderImplTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.paimon.table.source;
+
+import org.apache.paimon.table.InnerTable;
+import org.apache.paimon.types.DataTypes;
+import org.apache.paimon.types.RowType;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.ObjectStreamClass;
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Answers.CALLS_REAL_METHODS;
+import static org.mockito.Answers.RETURNS_SELF;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/** Tests for {@link ReadBuilderImpl}. */
+public class ReadBuilderImplTest {
+
+ @Test
+ public void testLongLimitCompatibilityBridge() {
+ ReadBuilder legacyBuilder = mock(ReadBuilder.class,
CALLS_REAL_METHODS);
+
+ legacyBuilder.withLimit((long) Integer.MAX_VALUE);
+ verify(legacyBuilder).withLimit(Integer.MAX_VALUE);
+
+ clearInvocations(legacyBuilder);
+
assertThat(legacyBuilder.withLimit(4294967297L)).isSameAs(legacyBuilder);
+ verify(legacyBuilder, never()).withLimit(anyInt());
+ }
+
+ @Test
+ public void testSerializedLimitFieldCompatibility() {
+
assertThat(ObjectStreamClass.lookup(ReadBuilderImpl.class).getField("limit").getType())
+ .isEqualTo(Integer.class);
+ }
+
+ @Test
+ public void testLongLimitForwardedToScanAndRead() {
+ InnerTable table = mock(InnerTable.class);
+ when(table.name()).thenReturn("table");
+ when(table.options()).thenReturn(Collections.emptyMap());
+ when(table.partitionKeys()).thenReturn(Collections.emptyList());
+ when(table.rowType()).thenReturn(RowType.of(DataTypes.INT()));
+
+ InnerTableScan scan = mock(InnerTableScan.class, RETURNS_SELF);
+ InnerTableRead read = mock(InnerTableRead.class, RETURNS_SELF);
+ when(table.newScan()).thenReturn(scan);
+ when(table.newRead()).thenReturn(read);
+
+ long limit = 4294967297L;
+ ReadBuilderImpl builder = new ReadBuilderImpl(table);
+ builder.withLimit(limit);
+ builder.newScan();
+ builder.newRead();
+
+ verify(scan).withLimit(limit);
+ verify(read).withLimit(limit);
+ }
+}
diff --git
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/FlinkSourceBuilder.java
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/FlinkSourceBuilder.java
index eb3da3e163..ec09badf9e 100644
---
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/FlinkSourceBuilder.java
+++
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/FlinkSourceBuilder.java
@@ -212,7 +212,7 @@ public class FlinkSourceBuilder {
readBuilder.withPartitionFilter(partitionPredicate);
}
if (limit != null) {
- readBuilder.withLimit(limit.intValue());
+ readBuilder.withLimit(limit);
}
return readBuilder.dropStats();
}
diff --git
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/source/FlinkSourceBuilderTest.java
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/source/FlinkSourceBuilderTest.java
index 709b0e733b..d2f708f2d1 100644
---
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/source/FlinkSourceBuilderTest.java
+++
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/source/FlinkSourceBuilderTest.java
@@ -31,8 +31,10 @@ import org.apache.paimon.table.Table;
import org.apache.paimon.table.source.DataSplit;
import org.apache.paimon.table.source.IncrementalSplit;
import org.apache.paimon.table.source.QueryAuthSplit;
+import org.apache.paimon.table.source.ReadBuilder;
import org.apache.paimon.table.source.Split;
import org.apache.paimon.types.DataTypes;
+import org.apache.paimon.types.RowType;
import org.apache.flink.api.dag.Transformation;
import org.apache.flink.runtime.plugable.SerializationDelegate;
@@ -58,6 +60,10 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Answers.RETURNS_SELF;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
/** Test for {@link FlinkSourceBuilder}. */
public class FlinkSourceBuilderTest {
@@ -153,6 +159,26 @@ public class FlinkSourceBuilderTest {
assertThat(transformation.getSource()).isInstanceOf(PaimonDataStreamSource.class);
}
+ @Test
+ public void testLongLimitForwardedToReadBuilder() {
+ Table table = mock(Table.class);
+ when(table.name()).thenReturn("table");
+ when(table.options())
+ .thenReturn(Collections.singletonMap("path",
tempDir.toUri().toString()));
+ when(table.primaryKeys()).thenReturn(Collections.emptyList());
+ when(table.rowType()).thenReturn(RowType.of(DataTypes.INT()));
+ ReadBuilder readBuilder = mock(ReadBuilder.class, RETURNS_SELF);
+ when(table.newReadBuilder()).thenReturn(readBuilder);
+
+ new FlinkSourceBuilder(table)
+ .env(StreamExecutionEnvironment.getExecutionEnvironment())
+ .sourceBounded(true)
+ .limit(4294967297L)
+ .build();
+
+ verify(readBuilder).withLimit(4294967297L);
+ }
+
@Test
public void testBuildWrapsContinuousSourceWithPaimonDataStreamSource()
throws Exception {
Table table = createTable("continuous_source", false, -1, true);