comphead opened a new issue, #3393:
URL: https://github.com/apache/iceberg-rust/issues/3393

   ### Apache Iceberg Rust version
   
   main @ `b2e90754b`
   
   ### Describe the bug
   
   parquet-rs reads a Parquet LIST as `ListView` or `LargeListView` when the 
file's embedded Arrow schema asks for it, for example in a file that arrow-rs 
wrote from a `ListViewArray`. The reader builds its projection mask by walking 
the Arrow schema with arrow-schema's `Fields::filter_leaves`, which treats 
`ListView` and `LargeListView` as leaves. Even when the file has field IDs at 
every level:
   
   - The list's element is never matched by its field ID, so the column reads 
as NULL whenever another column is projected.
   - A `ListView` of structs counts as one leaf where Parquet has one per 
struct field, so the leaf index of every later column is off. A plain column 
after it is read from the wrong Parquet leaf and comes back NULL.
   
   ### To Reproduce
   
   Added to the tests in `crates/iceberg/src/arrow/reader/projection.rs`:
   
   ```rust
   #[tokio::test]
   async fn list_view_shifts_projected_leaves() {
       use arrow_array::types::Int32Type;
       use arrow_array::{Int32Array, ListViewArray, StructArray};
       use arrow_buffer::ScalarBuffer;
   
       use crate::spec::ListType;
   
       let with_id = |field: Field, id: i32| {
           
field.with_metadata(HashMap::from([(PARQUET_FIELD_ID_META_KEY.to_string(), 
id.to_string())]))
       };
       let int = |id, name: &str| NestedField::optional(id, name, 
Type::Primitive(PrimitiveType::Int)).into();
   
       // 2: lv list_view<3: struct<4: a int, 5: b int>>, then 1: id int, with 
field IDs at every level.
       let a = Arc::new(with_id(Field::new("a", DataType::Int32, true), 4));
       let b = Arc::new(with_id(Field::new("b", DataType::Int32, true), 5));
       let structs = StructArray::new(
           vec![a, b].into(),
           vec![
               Arc::new(Int32Array::from(vec![10, 20, 30])) as ArrayRef,
               Arc::new(Int32Array::from(vec![-10, -20, -30])) as ArrayRef,
           ],
           None,
       );
       let element = Arc::new(with_id(Field::new("element", 
structs.data_type().clone(), true), 3));
       let lv = ListViewArray::new(
           element.clone(),
           ScalarBuffer::from(vec![0, 1, 2]),
           ScalarBuffer::from(vec![1, 1, 1]),
           Arc::new(structs),
           None,
       );
       let file_schema = Arc::new(ArrowSchema::new(vec![
           with_id(Field::new("lv", DataType::ListView(element), true), 2),
           with_id(Field::new("id", DataType::Int32, true), 1),
       ]));
       let batch = RecordBatch::try_new(file_schema.clone(), vec![
           Arc::new(lv) as ArrayRef,
           Arc::new(Int32Array::from(vec![1, 2, 3])) as ArrayRef,
       ])
       .unwrap();
       let tmp_dir = TempDir::new().unwrap();
       let path = format!("{}/1.parquet", tmp_dir.path().to_str().unwrap());
       let mut writer = ArrowWriter::try_new(File::create(&path).unwrap(), 
file_schema, None).unwrap();
       writer.write(&batch).unwrap();
       writer.close().unwrap();
   
       let element_type = Type::Struct(StructType::new(vec![int(4, "a"), int(5, 
"b")]));
       let element_field = NestedField::list_element(3, element_type, 
false).into();
       let schema = Arc::new(
           Schema::builder()
               .with_fields(vec![
                   NestedField::optional(2, "lv", 
Type::List(ListType::new(element_field))).into(),
                   int(1, "id"),
               ])
               .build()
               .unwrap(),
       );
       let reader = ArrowReaderBuilder::new(FileIO::new_with_fs(), 
Runtime::current()).build();
       for projection in [vec![1], vec![2, 1]] {
           let task = FileScanTask::builder()
               .with_file_size_in_bytes(std::fs::metadata(&path).unwrap().len())
               .with_start(0)
               .with_length(0)
               .with_data_file_path(path.clone())
               .with_data_file_format(DataFileFormat::Parquet)
               .with_schema(schema.clone())
               .with_project_field_ids(projection.clone())
               .with_case_sensitive(false)
               .build()
               .unwrap();
           let tasks = Box::pin(futures::stream::iter([Ok(task)])) as 
FileScanTaskStream;
           match 
reader.clone().read(tasks).unwrap().stream().try_collect::<Vec<RecordBatch>>().await
 {
               Ok(batches) => {
                   let id = 
batches[0].column_by_name("id").unwrap().as_primitive::<Int32Type>();
                   println!("{projection:?}: id = {:?}", 
id.iter().collect::<Vec<_>>());
               }
               Err(e) => println!("{projection:?}: {e}"),
           }
       }
   }
   ```
   
   ```text
   [1]: id = [None, None, None]
   [2, 1]: Unexpected => Arrow Schema Error, source: Invalid argument error: 
Incorrect number of arrays for StructArray fields, expected 2 got 1
   ```
   
   With `id int, lv list_view<int>` instead, projecting both columns returns 
`lv` as NULL in every row. Projecting only `lv` returns its values, because no 
leaf matches and the mask falls back to reading every column.
   
   ### Expected behavior
   
   The same results as for a `List` column: `id` is `[1, 2, 3]` for both 
projections, and `lv` returns its elements.
   
   One possible fix, not tried yet, is to rewrite `ListView` and 
`LargeListView` to `List` and `LargeList` in the schema hint the reader passes 
to parquet-rs, since Iceberg reads lists as `List` anyway.
   


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