diff --git a/extensions-contrib/druid-deltalake-extensions/src/main/java/org/apache/druid/delta/input/DeltaInputSourceReader.java b/extensions-contrib/druid-deltalake-extensions/src/main/java/org/apache/druid/delta/input/DeltaInputSourceReader.java
index 672a126f7c46..6acbb095f3bb 100644
--- a/extensions-contrib/druid-deltalake-extensions/src/main/java/org/apache/druid/delta/input/DeltaInputSourceReader.java
+++ b/extensions-contrib/druid-deltalake-extensions/src/main/java/org/apache/druid/delta/input/DeltaInputSourceReader.java
@@ -95,6 +95,13 @@ private static class DeltaInputSourceIterator implements CloseableIterator> filteredColumnarBatchIterators;
+ // Keep a reference to the current file's batch iterator so we drain ALL
+ // its batches before advancing to the next file.
+ // Bug fix for https://github.com/apache/druid/issues/18606:
+ // the original code used a local variable for filteredBatchIterator which
+ // was discarded on return, causing only the first batch (1024 rows) of each
+ // file to be read.
+ private io.delta.kernel.utils.CloseableIterator currentFileIterator = null;
private io.delta.kernel.utils.CloseableIterator currentBatch = null;
private final InputRowSchema inputRowSchema;
@@ -111,20 +118,34 @@ public DeltaInputSourceIterator(
public boolean hasNext()
{
while (currentBatch == null || !currentBatch.hasNext()) {
- if (!filteredColumnarBatchIterators.hasNext()) {
- return false; // No more batches or records to read!
- }
-
- final io.delta.kernel.utils.CloseableIterator filteredBatchIterator =
- filteredColumnarBatchIterators.next();
-
- while (filteredBatchIterator.hasNext()) {
- final FilteredColumnarBatch nextBatch = filteredBatchIterator.next();
+ // Drain remaining batches from the current file before moving to the next.
+ while (currentFileIterator != null && currentFileIterator.hasNext()) {
+ final FilteredColumnarBatch nextBatch = currentFileIterator.next();
currentBatch = nextBatch.getRows();
if (currentBatch.hasNext()) {
return true;
}
}
+
+ // Advance to the next file.
+ if (!filteredColumnarBatchIterators.hasNext()) {
+ return false;
+ }
+ // Close the drained file iterator before overwriting it. Each iterator from
+ // Scan.transformPhysicalData() owns an underlying Parquet reader/file handle;
+ // not closing it here would leak a handle per completed file on multi-file
+ // tables (only the last and the never-started iterators are closed in close()).
+ // hasNext() cannot throw checked exceptions, so wrap like the rest of this
+ // extension (see DeltaInputSource).
+ if (currentFileIterator != null) {
+ try {
+ currentFileIterator.close();
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ currentFileIterator = filteredColumnarBatchIterators.next();
}
return true;
}
@@ -146,8 +167,10 @@ public void close() throws IOException
if (currentBatch != null) {
currentBatch.close();
}
-
- if (filteredColumnarBatchIterators.hasNext()) {
+ if (currentFileIterator != null) {
+ currentFileIterator.close();
+ }
+ while (filteredColumnarBatchIterators.hasNext()) {
filteredColumnarBatchIterators.next().close();
}
}
diff --git a/extensions-contrib/druid-deltalake-extensions/src/test/java/org/apache/druid/delta/input/DeltaInputSourceTest.java b/extensions-contrib/druid-deltalake-extensions/src/test/java/org/apache/druid/delta/input/DeltaInputSourceTest.java
index cbbcaefb3ceb..6a689f31df59 100644
--- a/extensions-contrib/druid-deltalake-extensions/src/test/java/org/apache/druid/delta/input/DeltaInputSourceTest.java
+++ b/extensions-contrib/druid-deltalake-extensions/src/test/java/org/apache/druid/delta/input/DeltaInputSourceTest.java
@@ -439,6 +439,46 @@ private static List readAllRows(InputSourceReader reader) throws IOExc
return rows;
}
+ /**
+ * Regression test for https://github.com/apache/druid/issues/18606.
+ *
+ * {@link DeltaInputSourceReader.DeltaInputSourceIterator} used a local variable for the
+ * per-file {@code CloseableIterator}. When {@code hasNext()} returned
+ * after the first non-empty batch of a file, that iterator went out of scope. The next
+ * {@code hasNext()} call advanced to the next file, skipping all remaining batches of the
+ * current file. With the Delta kernel default batch size of 1024 rows this produced exactly
+ * {@code 1024 * numFiles} rows regardless of actual file size.
+ *
+ * Test table: 2 Parquet files x 2000 rows = 4000 rows total.
+ * Without the fix: 1024 x 2 = 2048 rows.
+ * With the fix: 4000 rows.
+ */
+ public static class BatchDrainRegressionTests
+ {
+ @Test
+ public void testAllRowsReturnedWhenFileExceedsOneBatch() throws IOException
+ {
+ final DeltaInputSource deltaInputSource = new DeltaInputSource(
+ LargeRowGroupDeltaTable.DELTA_TABLE_PATH,
+ null,
+ null,
+ null
+ );
+ final InputSourceReader inputSourceReader = deltaInputSource.reader(
+ LargeRowGroupDeltaTable.SCHEMA,
+ null,
+ null
+ );
+ final List rows = readAllRows(inputSourceReader);
+ Assert.assertEquals(
+ "Expected all rows to be read. "
+ + "If this fails with " + (1024 * 2) + " rows, the per-file batch drain bug (GH-18606) has regressed.",
+ LargeRowGroupDeltaTable.EXPECTED_ROW_COUNT,
+ rows.size()
+ );
+ }
+ }
+
private static void validateRows(
final List