rdblue commented on code in PR #17433:
URL: https://github.com/apache/iceberg/pull/17433#discussion_r3780063984


##########
core/src/main/java/org/apache/iceberg/V4ManifestReader.java:
##########
@@ -292,17 +338,70 @@ private Schema readSchema(boolean hasPartitionFilter) {
       if (columns != null) {
         Schema selected =
             caseSensitive ? fullSchema.select(columns) : 
fullSchema.caseInsensitiveSelect(columns);
-        return addRequiredColumns(selected, hasPartitionFilter);
+        return addRequiredColumns(fullSchema, selected, requiredStatsType, 
hasPartitionFilter);
       }
 
       if (requestedProjection != null) {
-        return addRequiredColumns(requestedProjection, hasPartitionFilter);
+        return addRequiredColumns(
+            fullSchema, requestedProjection, requiredStatsType, 
hasPartitionFilter);
       }
 
       return fullSchema;
     }
 
-    private Schema addRequiredColumns(Schema projection, boolean 
hasPartitionFilter) {
+    /** Returns the schema of everything this reader may read, including 
content stats. */
+    private Schema fullSchema(Types.StructType contentStatsType) {
+      Schema base = TrackedFile.schema(unionPartitionType, contentStatsType);
+      if (contentStatsType.fields().isEmpty()) {
+        // schema uses the unknown type for empty stats, which cannot be 
paired with the stats
+        // struct in the manifest, so drop the field instead of reading it as 
unknown
+        base = TypeUtil.selectNot(base, 
ImmutableSet.of(TrackedFile.CONTENT_STATS_ID));
+      }
+
+      // the read schema carries row_position (via BASE_TYPE) so the reader 
can fill manifestPos
+      return TypeUtil.replaceFieldTypes(
+          base, ImmutableMap.of(TrackedFile.TRACKING.fieldId(), 
TrackingStruct.BASE_TYPE));
+    }
+
+    /**
+     * Returns the stats type to read, which is empty when no stats are needed.
+     *
+     * <p>Stats for every field are read unless the caller narrows them with 
{@link
+     * #forScanPlanning()} or {@link #projectStats(Iterable)}, because copying 
entries into a new
+     * manifest needs all of them. A {@link #filter(Expression) filter} 
therefore never narrows the
+     * stats that are read; it only widens a set the caller has already 
narrowed.
+     */
+    private Types.StructType contentStatsType(Types.StructType 
requiredStatsType) {

Review Comment:
   I don't think the stats struct handling in this class is correct. There are 
a few uses of this class:
   
   1. Metadata tables (using `project`)
   2. Scan planning (using `forScanPlanning`)
   3. Table operations and manifest rewrites (no projection configured)
   4. User-driven API reads (using `select`)
   
   The content stats projection depends on those cases. For metadata tables, 
the schema passed in will be based on the current table manifest schema, so we 
don't have to worry about adding content stats. It's similar for `select`, but 
the projection happens in this class based on filters, requested stats columns, 
and the columns passed to `select`.
   
   Scan planning should use `StatsUtil.statsReadSchema`, passing in the 
required ID list (requested + row filter fields). That schema projects more 
fields than would be written, in case the data is present.
   
   Reads for table operations should produce all stats fields by using the 
table's current manifest schema. That is produced using `statsWriteSchema` and 
passing in the metrics config (and, later, other metadata).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to