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 b560965d fix(arrow): map NULL to false in decimal residual leaf masks 
(#1030)
b560965d is described below

commit b560965d046e0ce795b2d235019615c6743fdd9d
Author: Jiajia Li <[email protected]>
AuthorDate: Wed Oct 7 19:39:22 2026 +0800

    fix(arrow): map NULL to false in decimal residual leaf masks (#1030)
---
 crates/paimon/src/arrow/residual.rs | 60 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 59 insertions(+), 1 deletion(-)

diff --git a/crates/paimon/src/arrow/residual.rs 
b/crates/paimon/src/arrow/residual.rs
index 0cfa862d..d4a083e6 100644
--- a/crates/paimon/src/arrow/residual.rs
+++ b/crates/paimon/src/arrow/residual.rs
@@ -369,7 +369,7 @@ pub(crate) fn evaluate_exact_leaf_predicate(
     if matches!(array.data_type(), arrow_schema::DataType::Decimal128(_, _))
         && !matches!(op, PredicateOperator::IsNull | 
PredicateOperator::IsNotNull)
     {
-        return evaluate_decimal_leaf(array, op, literals);
+        return evaluate_decimal_leaf(array, op, 
literals).map(sanitize_filter_mask);
     }
     match op {
         PredicateOperator::IsNull => 
Ok(boolean_mask_from_predicate(array.len(), |row_index| {
@@ -2538,4 +2538,62 @@ mod tests {
             filter_record_batch_by_predicates(batch, &fp, 
std::slice::from_ref(&col)).unwrap();
         assert_eq!(out.num_rows(), 2, "d > 1.05 == d >= 1.1 -> {{1.1, 1.2}}");
     }
+
+    #[test]
+    fn test_nullable_decimal_composes() {
+        // A NULL decimal fails its leaf, so AND and OR combine masks without 
NULLs.
+        use crate::spec::DecimalType;
+        let decimal = || DataType::Decimal(DecimalType::with_nullable(true, 
10, 2).unwrap());
+        let col = DataField::new(0, "d".to_string(), decimal());
+        let schema = Arc::new(ArrowSchema::new(vec![ArrowField::new(
+            "d",
+            ArrowDataType::Decimal128(10, 2),
+            true,
+        )]));
+        // 1.00, NULL, 2.00 (unscaled at scale 2)
+        let arr = arrow_array::Decimal128Array::from(vec![Some(100), None, 
Some(200)])
+            .with_precision_and_scale(10, 2)
+            .unwrap();
+        let batch = RecordBatch::try_new(schema, vec![Arc::new(arr)]).unwrap();
+        let cmp = |op, unscaled| {
+            let literal = Datum::Decimal {
+                unscaled,
+                precision: 10,
+                scale: 2,
+            };
+            leaf(0, decimal(), op, vec![literal])
+        };
+        for (pred, expected) in [
+            // d > 0.50 AND d < 1.50 -> [1.00]
+            (
+                Predicate::And(vec![
+                    cmp(PredicateOperator::Gt, 50),
+                    cmp(PredicateOperator::Lt, 150),
+                ]),
+                vec![100i128],
+            ),
+            // d < 1.50 OR d > 1.50 -> [1.00, 2.00]
+            (
+                Predicate::Or(vec![
+                    cmp(PredicateOperator::Lt, 150),
+                    cmp(PredicateOperator::Gt, 150),
+                ]),
+                vec![100, 200],
+            ),
+        ] {
+            let fp = file_predicates(vec![pred], vec![col.clone()]);
+            let out =
+                filter_record_batch_by_predicates(batch.clone(), &fp, 
std::slice::from_ref(&col))
+                    .unwrap();
+            let values: Vec<i128> = out
+                .column(0)
+                .as_any()
+                .downcast_ref::<arrow_array::Decimal128Array>()
+                .unwrap()
+                .iter()
+                .flatten()
+                .collect();
+            assert_eq!(values, expected);
+        }
+    }
 }

Reply via email to