Skip to content

Core: Read content stats from v4 Manifest - #17433

Open
nastra wants to merge 10 commits into
apache:mainfrom
nastra:read-content-stats-from-manifest
Open

Core: Read content stats from v4 Manifest#17433
nastra wants to merge 10 commits into
apache:mainfrom
nastra:read-content-stats-from-manifest

Conversation

@nastra

@nastra nastra commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

V4ManifestReader now reads the content_stats column, so each TrackedFile comes
back with per-column bounds and counts.
The stats schema is derived from the table schema, so builder() takes it as a second
argument. Stats for every column are read by default, which is what copying entries into
a new manifest needs. Callers that want less can narrow:

  • projectStats(fieldIds) reads stats only for the given columns, projectStats() reads none
  • forScanPlanning() reads only what the filter needs
  • stats referenced by the filter are always read, even when select/project omits them

Reading stats surfaced two bugs in the copy path, fixed here:

  • ContentStatsStruct.copy() threw an NPE when a projected column had no stats in the
    manifest
  • FieldStatsStruct copied StructLike bounds by reference. Geometry and geography bound
    a bounding-box struct, so under reuseContainers() every entry reported the last row's
    bounds. Bounds are now deep-copied through StructLikeUtil.copy, and StructCopy is
    Serializable so a copied bound still survives serialization.

Used Claude for the initial prototyping but reviewed and adjusted the code manually

@github-actions github-actions Bot added the core label Jul 30, 2026
@nastra
nastra marked this pull request as draft July 30, 2026 15:45
@nastra
nastra force-pushed the read-content-stats-from-manifest branch from 0e72e44 to 44cadbc Compare July 30, 2026 15:53
@nastra
nastra force-pushed the read-content-stats-from-manifest branch from 44cadbc to c62305e Compare July 30, 2026 18:21
@nastra
nastra force-pushed the read-content-stats-from-manifest branch from c62305e to 6cf0fd0 Compare July 30, 2026 18:25
Comment thread core/src/test/java/org/apache/iceberg/TestV4ManifestReader.java Outdated
Comment thread core/src/main/java/org/apache/iceberg/V4ManifestReader.java Outdated
Comment thread core/src/test/java/org/apache/iceberg/TestV4ManifestReader.java Outdated
Comment thread core/src/main/java/org/apache/iceberg/V4ManifestReader.java Outdated
readBuilder.setCustomType(TrackedFile.CONTENT_STATS_ID, ContentStatsStruct.class);
// content_stats holds one stats struct per projected column
for (Types.NestedField fieldStats : statsField.type().asStructType().fields()) {
readBuilder.setCustomType(fieldStats.fieldId(), FieldStatsStruct.class);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does geo and variant need any special handling here? Can we add a test to make sure those types work?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, I've added tests for geo + variant types with single/multiple files and this uncovered a bug around Geo types copying, which I've fixed in FieldStatsStruct

@anoopj anoopj left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the file pruning coming in a followup?

@nastra
nastra force-pushed the read-content-stats-from-manifest branch from 5420c62 to ad7a0fe Compare July 31, 2026 07:46
@nastra
nastra force-pushed the read-content-stats-from-manifest branch from d6593b4 to 4975ce5 Compare July 31, 2026 10:17
@nastra
nastra force-pushed the read-content-stats-from-manifest branch from 4975ce5 to 33d52b2 Compare July 31, 2026 10:38
@nastra nastra moved this to In review in V4: metadata tree Jul 31, 2026
@nastra
nastra marked this pull request as ready for review July 31, 2026 10:54
* fields referenced by the {@link #filter(Expression) filter} are always read.
*/
Builder projectStats(Iterable<Integer> fieldIds) {
Preconditions.checkArgument(fieldIds != null, "Invalid stats projection for field IDs: null");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have 4 modes relevant to stats so far, the default/CDC, scan planing, select column by name and project schema and we conditionally add required column depends on the filter. I am wondering if we want to add coverage for

  • empty projectStats and filter (ok to use precondition to check if this combination does not make sense)
  • valid projectStats and filter with default mode ( I think scan planning is already covered in projectStatsAndFilterStatsAreCombined)

Comment on lines +290 to +291
Set<Integer> requiredFieldIds = requiredStatsProjectionForFieldIds();
Schema fullSchema = fullSchema(requiredFieldIds);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: curious if we shall build requiredStatsType for reading stats regardless of caller's column projection ahead of time instead of passing them to calculate multiple times later? Can potentially pass requiredStatsType to fullSchema() and addRequiredColumns().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

4 participants