This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new 3da16e36 perf(variant): Fix slow shredded residual reads (#1166)
3da16e36 is described below

commit 3da16e361d11955c075d2b70344928dce5eea013
Author: XiaoHongbo <[email protected]>
AuthorDate: Mon Oct 19 20:15:33 2026 +0800

    perf(variant): Fix slow shredded residual reads (#1166)
---
 crates/paimon/src/arrow/shredding/variant.rs | 336 ++++++++++++++--
 crates/paimon/src/variant.rs                 | 561 ++++++++++++++++++++++++++-
 2 files changed, 867 insertions(+), 30 deletions(-)

diff --git a/crates/paimon/src/arrow/shredding/variant.rs 
b/crates/paimon/src/arrow/shredding/variant.rs
index c0e5201d..5f462805 100644
--- a/crates/paimon/src/arrow/shredding/variant.rs
+++ b/crates/paimon/src/arrow/shredding/variant.rs
@@ -35,14 +35,15 @@ use crate::variant::{
 };
 use crate::{Error, Result};
 use arrow_array::{
-    new_null_array, Array, ArrayRef, BinaryArray, BooleanArray, Date32Array, 
Decimal128Array,
-    Float32Array, Float64Array, Int16Array, Int32Array, Int64Array, Int8Array, 
LargeBinaryArray,
-    ListArray, RecordBatch, StringArray, StructArray, 
TimestampMicrosecondArray,
-    TimestampMillisecondArray, TimestampNanosecondArray, TimestampSecondArray,
+    new_null_array, types::Int32Type, Array, ArrayRef, BinaryArray, 
BooleanArray, Date32Array,
+    Decimal128Array, DictionaryArray, Float32Array, Float64Array, Int16Array, 
Int32Array,
+    Int64Array, Int8Array, LargeBinaryArray, ListArray, RecordBatch, 
StringArray, StructArray,
+    TimestampMicrosecondArray, TimestampMillisecondArray, 
TimestampNanosecondArray,
+    TimestampSecondArray,
 };
 use arrow_buffer::{BooleanBuffer, NullBuffer, OffsetBuffer, ScalarBuffer};
 use arrow_schema::{DataType as ArrowDataType, Field as ArrowField, Fields, 
TimeUnit};
-use std::collections::HashMap;
+use std::collections::{HashMap, HashSet};
 use std::sync::{Arc, Mutex};
 
 const VARIANT_SHREDDING_SCHEMA_OPTION: &str = "variant.shreddingSchema";
@@ -909,18 +910,100 @@ pub(crate) fn assemble_variant_extraction_array(
     if let Some(projected) = assemble_typed_shredded_projection(input, fields, 
&metadata)? {
         return Ok(projected);
     }
+    if let Some(projected) = assemble_mixed_shredded_projection(input, fields, 
&metadata)? {
+        return Ok(projected);
+    }
     assemble_row_wise_variant_extraction(input, fields, &metadata)
 }
 
+/// Keep fully typed fields column-wise even when another requested field 
needs the
+/// residual path. A single residual must not force unrelated fields through 
the
+/// row-wise Variant decoder.
+fn assemble_mixed_shredded_projection(
+    input: &StructArray,
+    fields: &[DataField],
+    metadata: &[crate::spec::VariantFieldMetadata],
+) -> Result<Option<ArrayRef>> {
+    if !is_shredded_variant_array(input) {
+        return Ok(None);
+    }
+
+    let mut columns = Vec::with_capacity(fields.len());
+    let mut fallback_indices = Vec::new();
+    for (index, (field, field_metadata)) in 
fields.iter().zip(metadata).enumerate() {
+        let typed = typed_shredded_leaf(input, field_metadata.path())
+            .and_then(|leaf| cast_typed_leaf(leaf.as_ref(), field.data_type(), 
input.nulls()));
+        if typed.is_none() {
+            fallback_indices.push(index);
+        }
+        columns.push(typed);
+    }
+    if fallback_indices.is_empty() || fallback_indices.len() == fields.len() {
+        return Ok(None);
+    }
+    // The row-wise path must validate metadata for strict typed fields even
+    // when every residual field uses try casts.
+    if metadata.iter().any(|field| field.fail_on_error())
+        && fallback_indices
+            .iter()
+            .all(|&index| !metadata[index].fail_on_error())
+    {
+        return Ok(None);
+    }
+
+    let fallback_fields = fallback_indices
+        .iter()
+        .map(|&index| fields[index].clone())
+        .collect::<Vec<_>>();
+    let fallback_metadata = fallback_indices
+        .iter()
+        .map(|&index| metadata[index].clone())
+        .collect::<Vec<_>>();
+    let fallback =
+        assemble_row_wise_variant_extraction(input, &fallback_fields, 
&fallback_metadata)?;
+    let fallback = fallback.as_any().downcast_ref::<StructArray>().unwrap();
+    for (fallback_index, &field_index) in fallback_indices.iter().enumerate() {
+        columns[field_index] = Some(fallback.column(fallback_index).clone());
+    }
+
+    let arrow_fields: Fields = fields
+        .iter()
+        .map(|field| {
+            Ok(ArrowField::new(
+                field.name(),
+                paimon_type_to_arrow(field.data_type())?,
+                field.data_type().is_nullable(),
+            ))
+        })
+        .collect::<Result<Vec<_>>>()?
+        .into();
+    let validities = (0..input.len()).map(|row| input.is_valid(row)).collect();
+    let array = StructArray::try_new(
+        arrow_fields,
+        columns.into_iter().map(Option::unwrap).collect(),
+        Some(null_buffer(validities)),
+    )
+    .map_err(|e| Error::UnexpectedError {
+        message: format!("Failed to build mixed Variant extraction 
StructArray: {e}"),
+        source: Some(Box::new(e)),
+    })?;
+    Ok(Some(Arc::new(array)))
+}
+
 fn assemble_row_wise_variant_extraction(
     input: &StructArray,
     fields: &[DataField],
     metadata: &[crate::spec::VariantFieldMetadata],
 ) -> Result<ArrayRef> {
-    let materialized = materialize_variant_metadata(input)?;
-    let input = &materialized;
+    let shredded = is_shredded_variant_array(input);
+    let materialized = if shredded {
+        None
+    } else {
+        Some(materialize_variant_metadata(input)?)
+    };
+    let input = materialized.as_ref().unwrap_or(input);
 
-    let shredded_type = if is_shredded_variant_array(input) {
+    let shredded_type = if shredded {
         let DataType::Row(row) = arrow_to_paimon_type(input.data_type(), 
true)? else {
             unreachable!()
         };
@@ -931,6 +1014,10 @@ fn assemble_row_wise_variant_extraction(
     };
     let mut values_by_field = vec![Vec::with_capacity(input.len()); 
fields.len()];
     let mut validities = Vec::with_capacity(input.len());
+    // Arrow dictionary keys identify immutable metadata values within this 
batch.
+    // Once a residual value validates one key, other rows using that key do 
not
+    // need to compare and revalidate the same large dictionary bytes.
+    let mut validated_dictionary_keys = HashSet::new();
     for row in 0..input.len() {
         if input.is_null(row) {
             validities.push(false);
@@ -943,7 +1030,9 @@ fn assemble_row_wise_variant_extraction(
         validities.push(true);
         let shredded = shredded_type
             .as_ref()
-            .map(|(row_type, _)| struct_row_at(input, row, row_type))
+            .map(|(row_type, schema)| {
+                struct_row_at_skipping(input, row, row_type, 
schema.top_level_metadata_idx)
+            })
             .transpose()?
             .flatten();
         let variant = if shredded_type.is_none() {
@@ -951,33 +1040,79 @@ fn assemble_row_wise_variant_extraction(
         } else {
             None
         };
+        let (shredded_metadata, dictionary_key) = if shredded.is_some() {
+            match variant_metadata_at(input, row) {
+                Ok((bytes, key)) => (Some(bytes), key),
+                Err(error) => {
+                    if metadata.iter().any(|field| field.fail_on_error()) {
+                        return Err(error);
+                    }
+                    (None, None)
+                }
+            }
+        } else {
+            (None, None)
+        };
+        let mut metadata_validation = shredded_metadata.map(|bytes| {
+            crate::variant::VariantMetadataValidation::new(
+                bytes,
+                dictionary_key.is_some_and(|key| 
validated_dictionary_keys.contains(&key)),
+            )
+        });
         for (field_idx, field) in fields.iter().enumerate() {
             let field_metadata = &metadata[field_idx];
-            let extracted = match (&shredded, &shredded_type) {
+            let value = match (&shredded, &shredded_type) {
                 (Some(shredded), Some((_, schema))) => {
-                    crate::variant::extract_shredded_path(shredded, schema, 
field_metadata.path())
+                    let extracted = match &mut metadata_validation {
+                        Some(validation) => 
crate::variant::extract_shredded_path_cast(
+                            shredded,
+                            schema,
+                            validation,
+                            field_metadata.path(),
+                            field.data_type(),
+                            field_metadata.fail_on_error(),
+                        ),
+                        None => Ok(None),
+                    };
+                    match extracted {
+                        Ok(value) => value,
+                        Err(_) if !field_metadata.fail_on_error() => None,
+                        Err(error) => return Err(error),
+                    }
+                }
+                _ => {
+                    let extracted = match &variant {
+                        Some(variant) => {
+                            variant
+                                .get_path(field_metadata.path())
+                                .and_then(|selected| {
+                                    selected.map(|value| 
value.to_owned_variant()).transpose()
+                                })
+                        }
+                        None => Ok(None),
+                    };
+                    match extracted {
+                        Ok(Some(extracted)) => cast_variant_to_shredded_value(
+                            extracted.as_ref()?,
+                            field.data_type(),
+                            field_metadata.fail_on_error(),
+                        )?,
+                        Ok(None) => None,
+                        Err(_) if !field_metadata.fail_on_error() => None,
+                        Err(error) => return Err(error),
+                    }
                 }
-                _ => match &variant {
-                    Some(variant) => variant
-                        .get_path(field_metadata.path())
-                        .and_then(|selected| {
-                            selected.map(|value| 
value.to_owned_variant()).transpose()
-                        }),
-                    None => Ok(None),
-                },
-            };
-            let value = match extracted {
-                Ok(Some(extracted)) => cast_variant_to_shredded_value(
-                    extracted.as_ref()?,
-                    field.data_type(),
-                    field_metadata.fail_on_error(),
-                )?,
-                Ok(None) => None,
-                Err(_) if !field_metadata.fail_on_error() => None,
-                Err(error) => return Err(error),
             };
             values_by_field[field_idx].push(value);
         }
+        if metadata_validation
+            .as_ref()
+            .is_some_and(|validation| validation.is_validated())
+        {
+            if let Some(key) = dictionary_key {
+                validated_dictionary_keys.insert(key);
+            }
+        }
     }
 
     let arrow_fields: Fields = fields
@@ -1677,6 +1812,65 @@ fn struct_row_at(
     Ok(Some(shredded))
 }
 
+fn struct_row_at_skipping(
+    array: &StructArray,
+    row: usize,
+    row_type: &RowType,
+    skip_field: Option<usize>,
+) -> Result<Option<ShreddedRow>> {
+    if array.is_null(row) {
+        return Ok(None);
+    }
+    let mut shredded = ShreddedRow::new(row_type.fields().len());
+    for (field_idx, field) in row_type.fields().iter().enumerate() {
+        if Some(field_idx) == skip_field {
+            continue;
+        }
+        if let Some(value) = value_at(array.column(field_idx).as_ref(), row, 
field.data_type())? {
+            shredded.set(field_idx, value)?;
+        }
+    }
+    Ok(Some(shredded))
+}
+
+fn variant_metadata_at(input: &StructArray, row: usize) -> Result<(&[u8], 
Option<i32>)> {
+    let column = input
+        .column_by_name(VARIANT_METADATA_FIELD_NAME)
+        .ok_or_else(|| Error::DataInvalid {
+            message: "Shredded Variant metadata is missing".to_string(),
+            source: None,
+        })?;
+    if column.is_null(row) {
+        return Err(Error::DataInvalid {
+            message: "Shredded Variant metadata is null".to_string(),
+            source: None,
+        });
+    }
+    if let Some(binary) = column.as_any().downcast_ref::<BinaryArray>() {
+        return Ok((binary.value(row), None));
+    }
+    if let Some(dictionary) = 
column.as_any().downcast_ref::<DictionaryArray<Int32Type>>() {
+        let values = 
downcast_array::<BinaryArray>(dictionary.values().as_ref(), "Binary")?;
+        let key = dictionary.keys().value(row);
+        let index = usize::try_from(key).map_err(|_| Error::DataInvalid {
+            message: "Shredded Variant metadata dictionary key is 
negative".to_string(),
+            source: None,
+        })?;
+        if index >= values.len() || values.is_null(index) {
+            return Err(Error::DataInvalid {
+                message: "Shredded Variant metadata dictionary key is 
invalid".to_string(),
+                source: None,
+            });
+        }
+        return Ok((values.value(index), Some(key)));
+    }
+    Err(Error::DataInvalid {
+        message: "Shredded Variant metadata must be Binary or 
Dictionary(Int32, Binary)"
+            .to_string(),
+        source: None,
+    })
+}
+
 fn value_at(array: &dyn Array, row: usize, data_type: &DataType) -> 
Result<Option<ShreddedValue>> {
     if array.is_null(row) {
         return Ok(None);
@@ -2632,6 +2826,90 @@ mod tests {
         }
     }
 
+    #[test]
+    fn mixed_shredded_projection_keeps_typed_fields_column_wise() {
+        let input = shredded_for_test(
+            &[
+                Some(r#"{"a":0,"b":1}"#),
+                Some(r#"{"a":2.5e0,"b":2}"#),
+                Some(r#"{"b":3}"#),
+                None,
+            ],
+            r#"[{"name":"a","type":"DOUBLE"},{"name":"b","type":"BIGINT"}]"#,
+        );
+        let float = || DataType::Float(FloatType::new());
+        let (row_type, metadata) =
+            extraction_for_test(&[(float(), "$.b"), (float(), "$.a"), 
(float(), "$.b")]);
+        assert!(
+            assemble_typed_shredded_projection(&input, row_type.fields(), 
&metadata)
+                .unwrap()
+                .is_none()
+        );
+        assert!(typed_shredded_leaf(&input, "$.b").is_some());
+        assert!(typed_shredded_leaf(&input, "$.a").is_none());
+        let expected =
+            assemble_row_wise_variant_extraction(&input, row_type.fields(), 
&metadata).unwrap();
+        let actual =
+            
assemble_variant_extraction_array(&with_dictionary_metadata(&input), &row_type)
+                .unwrap();
+        assert_eq!(actual.as_ref(), expected.as_ref());
+    }
+
+    #[test]
+    fn mixed_shredded_projection_keeps_strict_metadata_error() {
+        let input = shredded_for_test(
+            &[Some(r#"{"a":0,"b":2}"#)],
+            r#"[{"name":"a","type":"DOUBLE"},{"name":"b","type":"BIGINT"}]"#,
+        );
+        let metadata_index = input
+            .fields()
+            .iter()
+            .position(|field| field.name() == VARIANT_METADATA_FIELD_NAME)
+            .unwrap();
+        let mut columns = input.columns().to_vec();
+        columns[metadata_index] = 
Arc::new(BinaryArray::from(vec![None::<&[u8]>]));
+        let mut fields = input
+            .fields()
+            .iter()
+            .map(|field| field.as_ref().clone())
+            .collect::<Vec<_>>();
+        fields[metadata_index] = 
fields[metadata_index].clone().with_nullable(true);
+        let missing_metadata =
+            StructArray::try_new(fields.into(), columns, 
input.nulls().cloned()).unwrap();
+        let row_type = variant_extraction_row(
+            true,
+            vec![
+                (
+                    DataType::Float(FloatType::new()),
+                    "$.b".to_string(),
+                    true,
+                    "UTC".to_string(),
+                ),
+                (
+                    DataType::Float(FloatType::new()),
+                    "$.a".to_string(),
+                    false,
+                    "UTC".to_string(),
+                ),
+            ],
+        )
+        .unwrap();
+        let metadata = row_type
+            .fields()
+            .iter()
+            .map(|field| 
parse_variant_metadata(field.description().unwrap()).unwrap())
+            .collect::<Vec<_>>();
+        assert!(typed_shredded_leaf(&missing_metadata, "$.b").is_some());
+        assert!(assemble_mixed_shredded_projection(
+            &missing_metadata,
+            row_type.fields(),
+            &metadata
+        )
+        .unwrap()
+        .is_none());
+        assert!(assemble_variant_extraction_array(&missing_metadata, 
&row_type).is_err());
+    }
+
     #[test]
     fn inferred_schema_transforms_and_reassembles_variant_batch() {
         let logical_fields = vec![
diff --git a/crates/paimon/src/variant.rs b/crates/paimon/src/variant.rs
index 20aa42b2..02c06781 100644
--- a/crates/paimon/src/variant.rs
+++ b/crates/paimon/src/variant.rs
@@ -17,7 +17,8 @@
 
 //! Java-compatible helpers for Paimon Variant values.
 
-use std::collections::{HashMap, HashSet};
+use std::cell::RefCell;
+use std::collections::{HashMap, HashSet, VecDeque};
 
 use crate::spec::{
     ArrayType, BigIntType, BooleanType, DataField, DataType, DateType, 
DecimalType, DoubleType,
@@ -36,6 +37,15 @@ const BASIC_TYPE_BITS: u8 = 2;
 const BASIC_TYPE_MASK: u8 = 0x3;
 const TYPE_INFO_MASK: u8 = 0x3f;
 const MAX_SHORT_STR_SIZE: usize = 0x3f;
+const MAX_CACHED_VARIANT_METADATA_BYTES: usize = 512 * 1024;
+const MAX_CACHED_VARIANT_METADATA_TOTAL_BYTES: usize = 1024 * 1024;
+const MAX_CACHED_VARIANT_METADATA_ENTRIES: usize = 4;
+
+thread_local! {
+    /// Exact-byte cache: metadata dictionaries are often repeated across rows 
and
+    /// independent projected expressions, but must never be trusted by hash 
alone.
+    static VALIDATED_METADATA: RefCell<VecDeque<Vec<u8>>> = const { 
RefCell::new(VecDeque::new()) };
+}
 
 const PRIMITIVE: u8 = 0;
 const SHORT_STR: u8 = 1;
@@ -128,12 +138,53 @@ pub struct VariantRef<'a> {
     pos: usize,
 }
 
+/// Validation state is tied to these exact metadata bytes, not just to a row 
or a
+/// dictionary key that may have a different meaning in another batch.
+pub(crate) struct VariantMetadataValidation<'a> {
+    bytes: &'a [u8],
+    validated: bool,
+}
+
+impl<'a> VariantMetadataValidation<'a> {
+    pub(crate) fn new(bytes: &'a [u8], validated: bool) -> Self {
+        Self { bytes, validated }
+    }
+
+    pub(crate) fn is_validated(&self) -> bool {
+        self.validated
+    }
+}
+
 impl<'a> VariantRef<'a> {
     pub fn new(value: &'a [u8], metadata: &'a [u8], pos: usize) -> 
Result<Self> {
         validate_payload(value, metadata)?;
         Self::new_at(value, metadata, pos)
     }
 
+    /// Validate the shared metadata once per shredded row, while still 
validating every
+    /// residual value before exposing a borrowed view of it.
+    fn new_with_cached_metadata(
+        value: &'a [u8],
+        pos: usize,
+        metadata: &mut VariantMetadataValidation<'a>,
+    ) -> Result<Self> {
+        if metadata.bytes.is_empty() || (metadata.bytes[0] & VERSION_MASK) != 
VERSION {
+            return data_invalid("Malformed Variant metadata version");
+        }
+        if value.len() > SIZE_LIMIT || metadata.bytes.len() > SIZE_LIMIT {
+            return data_invalid("Variant value or metadata exceeds constructor 
size limit");
+        }
+        if !metadata.validated {
+            validate_metadata(metadata.bytes)?;
+            metadata.validated = true;
+        }
+        let root_size = validate_value(value, metadata.bytes, 0, 0)?;
+        if root_size != value.len() {
+            return data_invalid("Malformed Variant root size");
+        }
+        Self::new_at(value, metadata.bytes, pos)
+    }
+
     fn new_at(value: &'a [u8], metadata: &'a [u8], pos: usize) -> Result<Self> 
{
         check_index(value, pos)?;
         Ok(Self {
@@ -442,6 +493,11 @@ pub fn validate_payload(value: &[u8], metadata: &[u8]) -> 
Result<()> {
 }
 
 fn validate_metadata(metadata: &[u8]) -> Result<()> {
+    if metadata.len() <= MAX_CACHED_VARIANT_METADATA_BYTES
+        && VALIDATED_METADATA.with(|cached| cached.borrow().iter().any(|entry| 
entry == metadata))
+    {
+        return Ok(());
+    }
     let offset_size = metadata_offset_size(metadata)?;
     let dict_size = read_unsigned(metadata, 1, offset_size)?;
     let offset_start = 1 + offset_size;
@@ -475,6 +531,20 @@ fn validate_metadata(metadata: &[u8]) -> Result<()> {
             return data_invalid("Malformed Variant metadata duplicate key");
         }
     }
+    if metadata.len() <= MAX_CACHED_VARIANT_METADATA_BYTES {
+        VALIDATED_METADATA.with(|cached| {
+            let mut cached = cached.borrow_mut();
+            let mut total_bytes = cached.iter().map(Vec::len).sum::<usize>();
+            while cached.len() >= MAX_CACHED_VARIANT_METADATA_ENTRIES
+                || total_bytes + metadata.len() > 
MAX_CACHED_VARIANT_METADATA_TOTAL_BYTES
+            {
+                if let Some(evicted) = cached.pop_front() {
+                    total_bytes -= evicted.len();
+                }
+            }
+            cached.push_back(metadata.to_vec());
+        });
+    }
     Ok(())
 }
 
@@ -2689,6 +2759,7 @@ pub(crate) fn rebuild_shredded(
 /// Resolve a path directly against a clipped shredded schema. Unrequested
 /// physical siblings and a pruned parent `value` are not needed to rebuild
 /// the selected subtree. Whole-value rebuilding remains strict.
+#[cfg(test)]
 pub(crate) fn extract_shredded_path(
     row: &ShreddedRow,
     schema: &VariantSchema,
@@ -2701,6 +2772,110 @@ pub(crate) fn extract_shredded_path(
     extract_shredded_segments(row, schema, metadata, &parse_path(path)?)
 }
 
+/// Cast a selected shredded path without copying the shared metadata for each
+/// residual field. The validation state is bound to one row's metadata bytes.
+pub(crate) fn extract_shredded_path_cast<'a>(
+    row: &'a ShreddedRow,
+    schema: &VariantSchema,
+    metadata: &mut VariantMetadataValidation<'a>,
+    path: &str,
+    data_type: &DataType,
+    fail_on_error: bool,
+) -> Result<Option<ShreddedValue>> {
+    if !shredded_row_has_value(row, schema) && schema.variant_idx.is_some() {
+        return data_invalid("Malformed shredded Variant: missing value");
+    }
+    extract_shredded_segments_cast(
+        row,
+        schema,
+        metadata,
+        &parse_path(path)?,
+        data_type,
+        fail_on_error,
+    )
+}
+
+fn extract_shredded_segments_cast<'a>(
+    row: &'a ShreddedRow,
+    schema: &VariantSchema,
+    metadata: &mut VariantMetadataValidation<'a>,
+    segments: &[PathSegment],
+    data_type: &DataType,
+    fail_on_error: bool,
+) -> Result<Option<ShreddedValue>> {
+    let Some((segment, remaining)) = segments.split_first() else {
+        // A selected scalar leaf can be read without rebuilding the whole
+        // Variant (and copying its shared metadata) for every field.
+        if schema.scalar_schema.is_some() {
+            let typed = schema.typed_idx.and_then(|index| row.field(index));
+            let residual = optional_binary_field(row, schema.variant_idx)?;
+            match (typed, residual) {
+                (None, Some(value)) => {
+                    let variant = VariantRef::new_with_cached_metadata(value, 
0, metadata)?;
+                    return cast_variant_to_shredded_value(variant, data_type, 
fail_on_error);
+                }
+                (None, None) => return Ok(None),
+                _ => {}
+            }
+        }
+        let extracted = extract_shredded_segments(row, schema, metadata.bytes, 
segments)?;
+        return extracted
+            .map(|variant| {
+                cast_variant_to_shredded_value(variant.as_ref()?, data_type, 
fail_on_error)
+            })
+            .transpose()
+            .map(Option::flatten);
+    };
+    if let Some(typed) = schema.typed_idx.and_then(|index| row.field(index)) {
+        match (segment, typed) {
+            (PathSegment::Key(key), ShreddedValue::Row(object))
+                if schema.object_schema.is_some() =>
+            {
+                if let Some(index) = schema.object_field_index(key) {
+                    let Some(ShreddedValue::Row(child)) = object.field(index) 
else {
+                        return data_invalid("Malformed shredded Variant object 
field");
+                    };
+                    return extract_shredded_segments_cast(
+                        child,
+                        &schema.object_schema.as_ref().unwrap()[index].schema,
+                        metadata,
+                        remaining,
+                        data_type,
+                        fail_on_error,
+                    );
+                }
+            }
+            (PathSegment::Index(index), ShreddedValue::List(elements))
+                if schema.array_schema.is_some() =>
+            {
+                return match elements.get(*index) {
+                    Some(element) => extract_shredded_segments_cast(
+                        element,
+                        schema.array_schema.as_ref().unwrap(),
+                        metadata,
+                        remaining,
+                        data_type,
+                        fail_on_error,
+                    ),
+                    None => Ok(None),
+                };
+            }
+            _ => {}
+        }
+    }
+    match optional_binary_field(row, schema.variant_idx)? {
+        Some(value) => {
+            let variant = VariantRef::new_with_cached_metadata(value, 0, 
metadata)?;
+            variant
+                .get_path_segments(segments)?
+                .map(|selected| cast_variant_to_shredded_value(selected, 
data_type, fail_on_error))
+                .transpose()
+                .map(Option::flatten)
+        }
+        None => Ok(None),
+    }
+}
+
 fn extract_shredded_segments(
     row: &ShreddedRow,
     schema: &VariantSchema,
@@ -3382,6 +3557,114 @@ mod tests {
         vec![VERSION, 0, 0]
     }
 
+    #[test]
+    fn metadata_cache_requires_exact_valid_bytes() {
+        let make_large = |suffix: char| {
+            let fields = (0..5342)
+                .map(|i| 
format!("\"field_{i:04}_{suffix}_{i:032}_abcdefghijklmnop\":0"))
+                .collect::<Vec<_>>()
+                .join(",");
+            GenericVariant::parse_json(&format!("{{{fields}}}"))
+                .unwrap()
+                .metadata()
+                .to_vec()
+        };
+        let first = make_large('a');
+        let second = make_large('b');
+        let third = GenericVariant::parse_json("42").unwrap();
+        assert_ne!(first, second);
+        assert!(first.len() > 256 * 1024);
+        VALIDATED_METADATA.with(|cached| cached.borrow_mut().clear());
+        validate_metadata(&first).unwrap();
+        validate_metadata(&second).unwrap();
+        validate_metadata(third.metadata()).unwrap();
+        let cached_pointers = VALIDATED_METADATA.with(|cached| {
+            cached
+                .borrow()
+                .iter()
+                .map(|metadata| metadata.as_ptr())
+                .collect::<Vec<_>>()
+        });
+        for _ in 0..100 {
+            validate_metadata(&first).unwrap();
+            validate_metadata(third.metadata()).unwrap();
+            validate_metadata(&second).unwrap();
+        }
+        VALIDATED_METADATA.with(|cached| {
+            assert_eq!(
+                cached
+                    .borrow()
+                    .iter()
+                    .map(|metadata| metadata.as_ptr())
+                    .collect::<Vec<_>>(),
+                cached_pointers,
+                "repeated dictionaries must remain cached instead of being 
rebuilt"
+            );
+        });
+
+        let mut malformed = first.clone();
+        *malformed.last_mut().unwrap() = 0xff;
+        assert_eq!(malformed.len(), first.len());
+        assert!(validate_metadata(&malformed).is_err());
+        validate_metadata(&first).unwrap();
+    }
+
+    #[test]
+    fn metadata_cache_respects_entry_and_byte_limits() {
+        VALIDATED_METADATA.with(|cached| cached.borrow_mut().clear());
+        for i in 0..5 {
+            let variant = 
GenericVariant::parse_json(&format!(r#"{{"field_{i}":0}}"#)).unwrap();
+            validate_metadata(variant.metadata()).unwrap();
+        }
+        VALIDATED_METADATA.with(|cached| {
+            let cached = cached.borrow();
+            assert_eq!(cached.len(), MAX_CACHED_VARIANT_METADATA_ENTRIES);
+            assert!(
+                cached.iter().map(Vec::len).sum::<usize>()
+                    <= MAX_CACHED_VARIANT_METADATA_TOTAL_BYTES
+            );
+        });
+
+        let wide = |suffix: usize, count: usize| {
+            let fields = (0..count)
+                .map(|i| 
format!(r#""field_{i:04}_{suffix}_{i:032}_abcdefghijklmnop":0"#))
+                .collect::<Vec<_>>()
+                .join(",");
+            GenericVariant::parse_json(&format!("{{{fields}}}"))
+                .unwrap()
+                .metadata()
+                .to_vec()
+        };
+        let large = wide(0, 5_342);
+        let oversize = wide(1, 9_000);
+        assert!(large.len() > MAX_CACHED_VARIANT_METADATA_TOTAL_BYTES / 4);
+        assert!(large.len() <= MAX_CACHED_VARIANT_METADATA_BYTES);
+        assert!(oversize.len() > MAX_CACHED_VARIANT_METADATA_BYTES);
+        VALIDATED_METADATA.with(|cached| cached.borrow_mut().clear());
+        for suffix in 0..4 {
+            let metadata = wide(suffix, 5_342);
+            validate_metadata(&metadata).unwrap();
+            VALIDATED_METADATA.with(|cached| {
+                let cached = cached.borrow();
+                assert!(cached.len() <= MAX_CACHED_VARIANT_METADATA_ENTRIES);
+                assert!(
+                    cached.iter().map(Vec::len).sum::<usize>()
+                        <= MAX_CACHED_VARIANT_METADATA_TOTAL_BYTES
+                );
+            });
+        }
+        VALIDATED_METADATA.with(|cached| {
+            assert!(!cached.borrow().iter().any(|entry| entry == &large));
+        });
+        let before = VALIDATED_METADATA.with(|cached| cached.borrow().len());
+        validate_metadata(&oversize).unwrap();
+        VALIDATED_METADATA.with(|cached| {
+            let cached = cached.borrow();
+            assert_eq!(cached.len(), before);
+            assert!(!cached.iter().any(|entry| entry == &oversize));
+        });
+    }
+
     fn simple_object_shredding_schema() -> VariantSchema {
         let configured = DataType::Row(RowType::new(vec![
             DataField::new(7, "age".to_string(), 
DataType::BigInt(BigIntType::new())),
@@ -3433,6 +3716,282 @@ mod tests {
         );
     }
 
+    #[test]
+    fn cached_shredded_residual_cast_matches_existing_extraction() {
+        let configured = DataType::Row(RowType::new(vec![
+            DataField::new(7, "a".to_string(), 
DataType::Double(DoubleType::new())),
+            DataField::new(8, "b".to_string(), 
DataType::Double(DoubleType::new())),
+        ]));
+        let DataType::Row(physical) = 
variant_shredding_type(&configured).unwrap() else {
+            panic!("expected row shredding type");
+        };
+        let schema = build_variant_schema(&physical).unwrap();
+        let target = DataType::Float(FloatType::new());
+        for json in [
+            r#"{"a":0,"b":1}"#,
+            r#"{"a":2.5,"b":0}"#,
+            r#"{"a":"bad","b":1}"#,
+            r#"{"a":null,"b":1}"#,
+            r#"{"b":1}"#,
+            r#"{"c":0}"#,
+            r#"{"d":0}"#,
+            "42",
+            "null",
+        ] {
+            let variant = GenericVariant::parse_json(json).unwrap();
+            let shredded = cast_shredded(&variant, &schema).unwrap();
+            for fail_on_error in [false, true] {
+                let mut metadata_validation =
+                    VariantMetadataValidation::new(variant.metadata(), false);
+                for path in ["$.a", "$.b"] {
+                    let expected =
+                        extract_shredded_path(&shredded, &schema, 
path).and_then(|selected| {
+                            selected
+                                .map(|value| {
+                                    cast_variant_to_shredded_value(
+                                        value.as_ref()?,
+                                        &target,
+                                        fail_on_error,
+                                    )
+                                })
+                                .transpose()
+                                .map(Option::flatten)
+                        });
+                    let actual = extract_shredded_path_cast(
+                        &shredded,
+                        &schema,
+                        &mut metadata_validation,
+                        path,
+                        &target,
+                        fail_on_error,
+                    );
+                    assert_eq!(
+                        actual.is_err(),
+                        expected.is_err(),
+                        "{json} {path} {fail_on_error}"
+                    );
+                    if let (Ok(actual), Ok(expected)) = (actual, expected) {
+                        assert_eq!(actual, expected, "{json} {path} 
{fail_on_error}");
+                    }
+                }
+            }
+        }
+    }
+
+    #[test]
+    fn 
cached_shredded_nested_object_and_array_paths_match_existing_extraction() {
+        let configured = DataType::Row(RowType::new(vec![
+            DataField::new(
+                7,
+                "object".to_string(),
+                DataType::Row(RowType::new(vec![DataField::new(
+                    8,
+                    "a".to_string(),
+                    DataType::Double(DoubleType::new()),
+                )])),
+            ),
+            DataField::new(
+                9,
+                "items".to_string(),
+                
DataType::Array(ArrayType::new(DataType::Double(DoubleType::new()))),
+            ),
+        ]));
+        let DataType::Row(physical) = 
variant_shredding_type(&configured).unwrap() else {
+            panic!("expected row shredding type");
+        };
+        let schema = build_variant_schema(&physical).unwrap();
+        let target = DataType::Float(FloatType::new());
+        for json in [
+            r#"{"object":{"a":0,"extra":1},"items":[0,2.5]}"#,
+            r#"{"object":{"a":2.5},"items":[3.5,0]}"#,
+            r#"{"object":null,"items":[]}"#,
+        ] {
+            let variant = GenericVariant::parse_json(json).unwrap();
+            let shredded = cast_shredded(&variant, &schema).unwrap();
+            let mut metadata = 
VariantMetadataValidation::new(variant.metadata(), false);
+            for path in ["$.object.a", "$.object.extra", "$.items[0]", 
"$.items[1]"] {
+                let expected = extract_shredded_path(&shredded, &schema, path)
+                    .unwrap()
+                    .and_then(|selected| {
+                        
cast_variant_to_shredded_value(selected.as_ref().unwrap(), &target, false)
+                            .unwrap()
+                    });
+                let actual = extract_shredded_path_cast(
+                    &shredded,
+                    &schema,
+                    &mut metadata,
+                    path,
+                    &target,
+                    false,
+                )
+                .unwrap();
+                assert_eq!(actual, expected, "{json} {path}");
+            }
+        }
+    }
+
+    #[test]
+    fn cached_shredded_residual_preserves_malformed_and_conflicting_values() {
+        let DataType::Row(physical) =
+            
variant_shredding_type(&DataType::Double(DoubleType::new())).unwrap()
+        else {
+            panic!("expected row shredding type");
+        };
+        let schema = build_variant_schema(&physical).unwrap();
+        let variant = GenericVariant::parse_json("0").unwrap();
+        let residual = cast_shredded(&variant, &schema).unwrap();
+        let target = DataType::Float(FloatType::new());
+        let compare = |row: &ShreddedRow, metadata_bytes: &[u8]| {
+            let expected = extract_shredded_path(row, &schema, 
"$").and_then(|selected| {
+                selected
+                    .map(|value| 
cast_variant_to_shredded_value(value.as_ref()?, &target, false))
+                    .transpose()
+                    .map(Option::flatten)
+            });
+            let mut metadata = VariantMetadataValidation::new(metadata_bytes, 
false);
+            let actual =
+                extract_shredded_path_cast(row, &schema, &mut metadata, "$", 
&target, false);
+            assert_eq!(actual.is_err(), expected.is_err());
+            if let (Ok(actual), Ok(expected)) = (actual, expected) {
+                assert_eq!(actual, expected);
+            }
+        };
+        compare(&residual, variant.metadata());
+
+        let mut bad_value = residual.clone();
+        bad_value
+            .set(
+                schema.variant_idx.unwrap(),
+                ShreddedValue::Binary(vec![0xff]),
+            )
+            .unwrap();
+        compare(&bad_value, variant.metadata());
+
+        let mut bad_metadata = residual.clone();
+        let mut bytes = variant.metadata().to_vec();
+        bytes.pop();
+        bad_metadata
+            .set(
+                schema.top_level_metadata_idx.unwrap(),
+                ShreddedValue::Binary(bytes.clone()),
+            )
+            .unwrap();
+        compare(&bad_metadata, &bytes);
+
+        let mut both = residual.clone();
+        both.set(schema.typed_idx.unwrap(), ShreddedValue::Float64(1.5))
+            .unwrap();
+        compare(&both, variant.metadata());
+    }
+
+    /// Decode-only comparison, deliberately excluding Parquet I/O and 
DataLoader work.
+    /// Run with `cargo test --release -p paimon --lib 
benchmark_wide_shredded_residual_cast -- --ignored --nocapture`.
+    #[test]
+    #[ignore = "local synthetic performance comparison"]
+    fn benchmark_wide_shredded_residual_cast() {
+        use std::hint::black_box;
+        use std::time::{Duration, Instant};
+
+        let configured = DataType::Row(RowType::new(
+            (0..200)
+                .map(|i| {
+                    DataField::new(i + 1, format!("f{i}"), 
DataType::Double(DoubleType::new()))
+                })
+                .collect(),
+        ));
+        let DataType::Row(physical) = 
variant_shredding_type(&configured).unwrap() else {
+            panic!("expected row shredding type");
+        };
+        let schema = build_variant_schema(&physical).unwrap();
+        let fields = (0..5_342)
+            .map(|i| {
+                if !(27..200).contains(&i) {
+                    format!(r#""f{i}":0"#)
+                } else {
+                    format!(r#""f{i}":{i}.5"#)
+                }
+            })
+            .collect::<Vec<_>>()
+            .join(",");
+        let variant = 
GenericVariant::parse_json(&format!("{{{fields}}}")).unwrap();
+        let shredded = cast_shredded(&variant, &schema).unwrap();
+        let Some(ShreddedValue::Row(object)) =
+            schema.typed_idx.and_then(|index| shredded.field(index))
+        else {
+            panic!("expected shredded object");
+        };
+        for field in schema.object_schema.as_ref().unwrap().iter().take(27) {
+            let index = schema.object_field_index(&field.field_name).unwrap();
+            let Some(ShreddedValue::Row(child)) = object.field(index) else {
+                panic!("expected shredded child");
+            };
+            assert!(child.field(field.schema.variant_idx.unwrap()).is_some());
+        }
+        let paths = (0..27).map(|i| format!("$.f{i}")).collect::<Vec<_>>();
+        let target = DataType::Float(FloatType::new());
+        for path in &paths {
+            let old = extract_shredded_path(&shredded, &schema, path)
+                .unwrap()
+                .and_then(|value| {
+                    cast_variant_to_shredded_value(value.as_ref().unwrap(), 
&target, false).unwrap()
+                });
+            let mut metadata = 
VariantMetadataValidation::new(variant.metadata(), false);
+            let new =
+                extract_shredded_path_cast(&shredded, &schema, &mut metadata, 
path, &target, false)
+                    .unwrap();
+            assert_eq!(new, old);
+        }
+
+        let mut old_times = Vec::new();
+        let mut new_times = Vec::new();
+        for round in 0..5 {
+            let measure = |optimized: bool| {
+                let start = Instant::now();
+                for _ in 0..512 {
+                    let mut metadata = 
VariantMetadataValidation::new(variant.metadata(), false);
+                    for path in &paths {
+                        let value = if optimized {
+                            extract_shredded_path_cast(
+                                &shredded,
+                                &schema,
+                                &mut metadata,
+                                path,
+                                &target,
+                                false,
+                            )
+                            .unwrap()
+                        } else {
+                            extract_shredded_path(&shredded, &schema, path)
+                                .unwrap()
+                                .and_then(|value| {
+                                    cast_variant_to_shredded_value(
+                                        value.as_ref().unwrap(),
+                                        &target,
+                                        false,
+                                    )
+                                    .unwrap()
+                                })
+                        };
+                        black_box(value);
+                    }
+                }
+                start.elapsed()
+            };
+            if round % 2 == 0 {
+                old_times.push(measure(false));
+                new_times.push(measure(true));
+            } else {
+                new_times.push(measure(true));
+                old_times.push(measure(false));
+            }
+        }
+        old_times.sort();
+        new_times.sort();
+        let old: Duration = old_times[old_times.len() / 2];
+        let new: Duration = new_times[new_times.len() / 2];
+        println!("512 synthetic rows, 5342 metadata keys, 27 residual fields 
of 200 projected: legacy={old:?}, direct={new:?}");
+    }
+
     #[test]
     fn variant_shredding_schema_matches_java_shape() {
         let configured = DataType::Row(RowType::new(vec![

Reply via email to