kosiew commented on code in PR #23391:
URL: https://github.com/apache/datafusion/pull/23391#discussion_r3548595384


##########
datafusion/datasource-parquet/src/row_filter.rs:
##########
@@ -2141,4 +2292,77 @@ mod test {
         let batch = RecordBatch::new_empty(Arc::clone(table_schema));
         expr.evaluate(&batch).is_ok()
     }
+
+    fn list_of(inner: DataType) -> DataType {
+        DataType::List(Arc::new(Field::new("element", inner, true)))
+    }
+
+    fn struct_of(fields: Vec<Field>) -> DataType {
+        DataType::Struct(fields.into_iter().map(Arc::new).collect::<Fields>())
+    }
+
+    #[test]

Review Comment:
   The added tests exercise `prune_and_collect` directly, which is helpful, but 
they do not quite cover the main behavior this PR changes: recognizing a real 
`CastExpr(Column, narrow_type)` projection and turning it into the correct read 
plan.
   
   A regression in `try_nested_projection_leaves` dispatch, 
`expr.data_type(file_schema)`, column rebasing, `ProjectionMask::leaves`, or 
`projected_schema` construction would not be caught by the current tests.
   
   Could you please add at least one integration-level regression test through 
`build_projection_read_plan` or `DecoderProjection::try_new` using the real 
`CastExpr(Column, narrow_type)` shape, and assert the leaf mask and projected 
schema for `array<struct<a,b>> -> array<struct<a>>`?
   
   Since the PR explicitly handles maps, it would also be good to include a 
narrowed map value struct case. Otherwise, we should probably defer map support 
if it is not intended to be covered here.



##########
datafusion/datasource-parquet/src/row_filter.rs:
##########
@@ -732,6 +743,147 @@ pub(crate) fn build_projection_read_plan(
     }
 }
 
+/// `field` rebuilt with a new data type, preserving its name, nullability, 
and metadata.
+fn with_type(field: &Field, dt: DataType) -> Arc<Field> {
+    Arc::new(field.clone().with_data_type(dt))
+}
+
+/// Element type of `pruned` if it is a list variant.
+fn pruned_list_element(pruned: Option<&DataType>) -> Option<&DataType> {
+    match pruned {
+        Some(
+            DataType::List(f) | DataType::LargeList(f) | 
DataType::FixedSizeList(f, _),
+        ) => Some(f.data_type()),
+        _ => None,
+    }
+}
+
+/// Prune `full` to the subtree present in `pruned`, advancing `*leaf` across 
every
+/// primitive of `full` in parquet leaf order and recording the kept indices 
in `out`.
+/// Names and nullability come from `full` so the result matches the decoder's 
output.
+/// Returns `None` when no leaf under `full` is kept, which lets callers 
detect a
+/// request that is not a structural subtree (e.g. a `get_field` extraction) 
and defer.
+fn prune_and_collect(
+    full: &DataType,
+    pruned: Option<&DataType>,
+    leaf: &mut usize,
+    out: &mut Vec<usize>,
+) -> Option<DataType> {
+    match full {
+        DataType::Struct(fields) => {
+            let pruned = match pruned {
+                Some(DataType::Struct(p)) => Some(p),
+                _ => None,
+            };
+            let kept: Fields = fields
+                .iter()
+                .filter_map(|f| {
+                    let child = pruned
+                        .and_then(|p| p.iter().find(|x| x.name() == f.name()))
+                        .map(|x| x.data_type());
+                    Some(with_type(
+                        f,
+                        prune_and_collect(f.data_type(), child, leaf, out)?,
+                    ))
+                })
+                .collect();
+            (!kept.is_empty()).then_some(DataType::Struct(kept))
+        }
+        DataType::List(f) => {
+            prune_and_collect(f.data_type(), pruned_list_element(pruned), 
leaf, out)
+                .map(|dt| DataType::List(with_type(f, dt)))
+        }
+        DataType::LargeList(f) => {
+            prune_and_collect(f.data_type(), pruned_list_element(pruned), 
leaf, out)
+                .map(|dt| DataType::LargeList(with_type(f, dt)))
+        }
+        DataType::FixedSizeList(f, n) => {
+            prune_and_collect(f.data_type(), pruned_list_element(pruned), 
leaf, out)
+                .map(|dt| DataType::FixedSizeList(with_type(f, dt), *n))
+        }
+        DataType::Map(entries, sorted) => {
+            let child = match pruned {
+                Some(DataType::Map(p, _)) => Some(p.data_type()),
+                _ => None,
+            };
+            prune_and_collect(entries.data_type(), child, leaf, out)
+                .map(|dt| DataType::Map(with_type(entries, dt), *sorted))
+        }
+        _ => {
+            if pruned.is_some() {
+                out.push(*leaf);
+            }
+            *leaf += 1;
+            pruned.map(|_| full.clone())
+        }
+    }
+}
+
+/// Build a leaf-pruned read plan for projections that narrow nested columns, 
or `None`
+/// to defer to the generic path. Fires only when every expression projects a 
single
+/// distinct file column and at least one requests a nested type narrower than 
the
+/// file's; then only the leaves reached by each requested type are read. 
Keyed off
+/// `expr.data_type`, so it is independent of the concrete projection 
expression.
+fn try_nested_projection_leaves(
+    exprs: &[Arc<dyn PhysicalExpr>],
+    file_schema: &Schema,
+    schema_descr: &SchemaDescriptor,
+) -> Option<ParquetReadPlan> {
+    // First parquet leaf index of each root column. Iterating leaves high to 
low, each
+    // root's slot ends holding its lowest leaf index, where prune_and_collect 
starts.
+    let mut offsets = vec![0usize; file_schema.fields().len()];
+    for leaf in (0..schema_descr.num_columns()).rev() {
+        offsets[schema_descr.get_column_root_idx(leaf)] = leaf;
+    }
+
+    let mut leaves = Vec::new();
+    let mut projected: Vec<(usize, Arc<Field>)> = Vec::new();
+    let mut roots = std::collections::HashSet::new();

Review Comment:
   Small style thought: consider importing `HashSet` with the existing 
collection imports, or using `BTreeSet` consistently in this file. Not a 
blocker, but it would keep this helper visually aligned with the surrounding 
projection-pruning code.



-- 
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