Normalize a semi-structured [`RecordBatch`] into a flat table. Nested [`Field`]s will generate names separated by `separator`, up to a depth of `max_level` (unlimited if `None`). e.g. given a [`RecordBatch`] with schema: ```text "foo": StructArray<"bar": Utf8> ``` A separator of `"."` would generate a batch with the schema: ```text "foo.bar": Utf8 ``` Note that giving a depth of `Some(0)` to
(&self, separator: &str, max_level: Option<usize>)
| 539 | /// assert_eq!(expected, normalized); |
| 540 | /// ``` |
| 541 | pub fn normalize(&self, separator: &str, max_level: Option<usize>) -> Result<Self, ArrowError> { |
| 542 | let max_level = match max_level.unwrap_or(usize::MAX) { |
| 543 | 0 => usize::MAX, |
| 544 | val => val, |
| 545 | }; |
| 546 | let mut stack: Vec<(usize, ArrayRef, String, FieldRef)> = self |
| 547 | .columns |
| 548 | .iter() |
| 549 | .zip(self.schema.fields()) |
| 550 | .rev() |
| 551 | .map(|(c, f)| (0, c.clone(), f.name().clone(), Arc::clone(f))) |
| 552 | .collect(); |
| 553 | let mut columns: Vec<ArrayRef> = Vec::new(); |
| 554 | let mut fields: Vec<FieldRef> = Vec::new(); |
| 555 | |
| 556 | while let Some((depth, c, name, field_ref)) = stack.pop() { |
| 557 | match field_ref.data_type() { |
| 558 | DataType::Struct(_) if depth < max_level => { |
| 559 | let (flat_fields, flat_cols) = c.as_struct().flatten(); |
| 560 | for (cff, fff) in flat_cols.into_iter().zip(flat_fields.iter()).rev() { |
| 561 | let child_name = format!("{name}{separator}{}", fff.name()); |
| 562 | stack.push((depth + 1, cff, child_name, Arc::clone(fff))) |
| 563 | } |
| 564 | } |
| 565 | _ => { |
| 566 | let updated_field = |
| 567 | Field::new(name, field_ref.data_type().clone(), field_ref.is_nullable()); |
| 568 | columns.push(c); |
| 569 | fields.push(Arc::new(updated_field)); |
| 570 | } |
| 571 | } |
| 572 | } |
| 573 | RecordBatch::try_new(Arc::new(Schema::new(fields)), columns) |
| 574 | } |
| 575 | |
| 576 | /// Returns the number of columns in the record batch. |
| 577 | /// |