This is an automated email from the ASF dual-hosted git repository.
amogh-jahagirdar pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg.git
The following commit(s) were added to refs/heads/main by this push:
new 94f318dc99 Core: Scope V4ManifestReader row ID assignment to each
iterator (#18795)
94f318dc99 is described below
commit 94f318dc9949473c7f39faff98667a35823dca24
Author: Anoop Johnson <[email protected]>
AuthorDate: Thu Nov 5 14:32:23 2026 -0800
Core: Scope V4ManifestReader row ID assignment to each iterator (#18795)
---
.../java/org/apache/iceberg/V4ManifestReader.java | 50 ++++++++++++++--------
.../org/apache/iceberg/TestV4ManifestReader.java | 27 ++++++++++++
2 files changed, 60 insertions(+), 17 deletions(-)
diff --git a/core/src/main/java/org/apache/iceberg/V4ManifestReader.java
b/core/src/main/java/org/apache/iceberg/V4ManifestReader.java
index a91c32d4ff..42b426ae07 100644
--- a/core/src/main/java/org/apache/iceberg/V4ManifestReader.java
+++ b/core/src/main/java/org/apache/iceberg/V4ManifestReader.java
@@ -22,6 +22,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Function;
import org.apache.iceberg.expressions.Binder;
import org.apache.iceberg.expressions.Evaluator;
import org.apache.iceberg.expressions.Expression;
@@ -80,8 +81,6 @@ class V4ManifestReader extends CloseableGroup implements
CloseableIterable<Track
private final boolean isUncommitted;
private final ScanMetrics scanMetrics;
- private Long nextRowId = null;
-
private V4ManifestReader(
ManifestFile manifest,
FileIO io,
@@ -104,9 +103,6 @@ class V4ManifestReader extends CloseableGroup implements
CloseableIterable<Track
this.requestedStatsFieldIds = requestedStatsFieldIds;
this.isUncommitted = isUncommitted;
this.scanMetrics = scanMetrics;
-
- // initialize the next row ID to the manifest's first row ID
- this.nextRowId = manifest.firstRowId();
}
@VisibleForTesting
@@ -118,8 +114,7 @@ class V4ManifestReader extends CloseableGroup implements
CloseableIterable<Track
public CloseableIterator<TrackedFile> iterator() {
// first row ID assignment must happen first to maintain consistent
assignment when files are
// removed or filtered
- CloseableIterable<TrackedFile> files =
- CloseableIterable.transform(open(), this::applyInheritance);
+ CloseableIterable<TrackedFile> files = CloseableIterable.transform(open(),
applyInheritance());
files = CloseableIterable.transform(files, this::projectPartition);
@@ -151,20 +146,41 @@ class V4ManifestReader extends CloseableGroup implements
CloseableIterable<Track
return CloseableIterable.transform(files, this::copyResolved).iterator();
}
- private TrackedFile applyInheritance(TrackedFile file) {
- // the reader uses TrackingStruct to read tracking so this cast is safe
- TrackingStruct tracking = (TrackingStruct) file.tracking();
+ private Function<TrackedFile, TrackedFile> applyInheritance() {
if (isUncommitted) {
// uncommitted files cannot have a sequence number or assign first row ID
- tracking.inherit(manifest.snapshotId());
- } else {
- tracking.inherit(manifest.snapshotId(), manifest.sequenceNumber());
- if (tracking.assignFirstRowId(nextRowId)) {
- this.nextRowId += file.recordCount();
- }
+ return file -> {
+ // the reader uses TrackingStruct to read tracking so this cast is safe
+ ((TrackingStruct) file.tracking()).inherit(manifest.snapshotId());
+ return file;
+ };
+ }
+
+ Long firstRowId = manifest.firstRowId();
+ if (firstRowId != null) {
+ return new Function<>() {
+ private long nextRowId = firstRowId;
+
+ @Override
+ public TrackedFile apply(TrackedFile file) {
+ TrackingStruct tracking = (TrackingStruct) file.tracking();
+ tracking.inherit(manifest.snapshotId(), manifest.sequenceNumber());
+ if (tracking.assignFirstRowId(nextRowId)) {
+ nextRowId += file.recordCount();
+ }
+
+ return file;
+ }
+ };
}
- return file;
+ // committed manifest with null manifest-level first row ID (pre-v3
upgrade path)
+ return file -> {
+ TrackingStruct tracking = (TrackingStruct) file.tracking();
+ tracking.inherit(manifest.snapshotId(), manifest.sequenceNumber());
+ tracking.assignFirstRowId(null);
+ return file;
+ };
}
private TrackedFile projectPartition(TrackedFile file) {
diff --git a/core/src/test/java/org/apache/iceberg/TestV4ManifestReader.java
b/core/src/test/java/org/apache/iceberg/TestV4ManifestReader.java
index 864a0e1eec..1dff57e04c 100644
--- a/core/src/test/java/org/apache/iceberg/TestV4ManifestReader.java
+++ b/core/src/test/java/org/apache/iceberg/TestV4ManifestReader.java
@@ -54,6 +54,7 @@ import org.apache.iceberg.expressions.Expression;
import org.apache.iceberg.expressions.Expressions;
import org.apache.iceberg.geospatial.GeospatialBound;
import org.apache.iceberg.inmemory.InMemoryFileIO;
+import org.apache.iceberg.io.CloseableIterator;
import org.apache.iceberg.io.OutputFile;
import org.apache.iceberg.metrics.DefaultMetricsContext;
import org.apache.iceberg.metrics.ScanMetrics;
@@ -720,6 +721,32 @@ class TestV4ManifestReader {
.isEqualTo(expected);
}
+ @ParameterizedTest
+ @FieldSource("MANIFEST_FORMATS")
+ void inheritanceFirstRowIdIsIndependentBetweenIterators(FileFormat format)
throws IOException {
+ TrackedFile fileA = dataFile(ADDED_TRACKING,
"s3://bucket/table/file-a.parquet");
+ TrackedFile fileB = dataFile(ADDED_TRACKING,
"s3://bucket/table/file-b.parquet");
+
+ ManifestFile manifest =
+ writeManifest(format, UNPARTITIONED_TYPE, ImmutableList.of(fileA,
fileB));
+ when(manifest.firstRowId()).thenReturn(10_000L);
+
+ V4ManifestReader.Builder builder =
+ V4ManifestReader.builder(manifest, IO, TABLE_SCHEMA,
ID_PARTITIONING_SPECS)
+ .metricsConfig(METRICS_CONFIG);
+
+ try (V4ManifestReader reader = builder.build();
+ CloseableIterator<TrackedFile> first = reader.iterator();
+ CloseableIterator<TrackedFile> second = reader.iterator()) {
+ assertThat(first.next().tracking().firstRowId()).isEqualTo(10_000L);
+ assertThat(second.next().tracking().firstRowId())
+ .as("a second iterator assigns from the manifest's first row ID, not
the first's counter")
+ .isEqualTo(10_000L);
+ assertThat(first.next().tracking().firstRowId()).isEqualTo(10_000L +
RECORD_COUNT);
+ assertThat(second.next().tracking().firstRowId()).isEqualTo(10_000L +
RECORD_COUNT);
+ }
+ }
+
@ParameterizedTest
@FieldSource("MANIFEST_FORMATS")
public void inheritanceUncommittedSkipsFirstRowId(FileFormat format) throws
IOException {