tustvold commented on code in PR #5217:
URL: https://github.com/apache/arrow-rs/pull/5217#discussion_r1436952672


##########
arrow-ord/src/cmp.rs:
##########
@@ -198,15 +267,30 @@ fn compare_op(op: Op, lhs: &dyn Datum, rhs: &dyn Datum) 
-> Result<BooleanArray,
     let r = r_v.map(|x| x.values().as_ref()).unwrap_or(r);
     let r_t = r.data_type();
 
-    if l_t != r_t || l_t.is_nested() {
+    if l_t.is_nested() {
+        if !l_t.equals_datatype(r_t) {
+            return Err(ArrowError::InvalidArgumentError(format!(
+                "Invalid comparison operation: {l_t} {op} {r_t}"
+            )));
+        }
+        match (l_t, op) {
+            (Struct(_), Op::Equal | Op::NotEqual | Op::Distinct | 
Op::NotDistinct) => {}
+            _ => {
+                return Err(ArrowError::InvalidArgumentError(format!(
+                    "Invalid comparison operation: {l_t} {op} {r_t}"
+                )));
+            }
+        }
+    } else if r_t != l_t {
         return Err(ArrowError::InvalidArgumentError(format!(
             "Invalid comparison operation: {l_t} {op} {r_t}"
         )));
     }

Review Comment:
   ```suggestion
       if !l_t.equals_datatype(r_t) {
           return Err(ArrowError::InvalidArgumentError(format!(
               "Invalid comparison operation: {l_t} {op} {r_t}"
           )));
       }
   ```



##########
arrow-ord/src/cmp.rs:
##########
@@ -215,76 +299,114 @@ fn compare_op(op: Op, lhs: &dyn Datum, rhs: &dyn Datum) 
-> Result<BooleanArray,
             (LargeBinary, LargeBinary) => apply(op, l.as_binary::<i64>(), l_s, 
l_v, r.as_binary::<i64>(), r_s, r_v),
             (FixedSizeBinary(_), FixedSizeBinary(_)) => apply(op, 
l.as_fixed_size_binary(), l_s, l_v, r.as_fixed_size_binary(), r_s, r_v),
             (Null, Null) => None,
+            (Struct(_), Struct(_)) => Some(compare_op_struct_values(op, l, 
l_s, r, r_s, len)?),
             _ => unreachable!(),
         };
-        d.unwrap_or_else(|| BooleanBuffer::new_unset(len))
+        Ok(values.unwrap_or_else(|| BooleanBuffer::new_unset(len)))
     };
-
-    let l_nulls = l_nulls.filter(|n| n.null_count() > 0);
-    let r_nulls = r_nulls.filter(|n| n.null_count() > 0);
     Ok(match (l_nulls, l_s, r_nulls, r_s) {
-        (Some(l), true, Some(r), true) | (Some(l), false, Some(r), false) => {
+        (Some(l_nulls), true, Some(r_nulls), true)
+        | (Some(l_nulls), false, Some(r_nulls), false) => {
             // Either both sides are scalar or neither side is scalar
             match op {
                 Op::Distinct => {
-                    let values = values();
-                    let l = l.inner().bit_chunks().iter_padded();
-                    let r = r.inner().bit_chunks().iter_padded();
+                    let values = values()?;
+                    let l_nulls = l_nulls.inner().bit_chunks().iter_padded();
+                    let r_nulls = r_nulls.inner().bit_chunks().iter_padded();
                     let ne = values.bit_chunks().iter_padded();
 
-                    let c = |((l, r), n)| ((l ^ r) | (l & r & n));
-                    let buffer = l.zip(r).zip(ne).map(c).collect();
-                    BooleanBuffer::new(buffer, 0, len).into()
+                    let c =
+                        |((l_nulls, r_nulls), n)| ((l_nulls ^ r_nulls) | 
(l_nulls & r_nulls & n));
+                    let buffer = l_nulls.zip(r_nulls).zip(ne).map(c).collect();
+                    BooleanBuffer::new(buffer, 0, len)
                 }
                 Op::NotDistinct => {
-                    let values = values();
-                    let l = l.inner().bit_chunks().iter_padded();
-                    let r = r.inner().bit_chunks().iter_padded();
+                    let values = values()?;
+                    let l_nulls = l_nulls.inner().bit_chunks().iter_padded();
+                    let r_nulls = r_nulls.inner().bit_chunks().iter_padded();
                     let e = values.bit_chunks().iter_padded();
 
-                    let c = |((l, r), e)| u64::not(l | r) | (l & r & e);
-                    let buffer = l.zip(r).zip(e).map(c).collect();
-                    BooleanBuffer::new(buffer, 0, len).into()
+                    let c = |((l_nulls, r_nulls), e)| {
+                        u64::not(l_nulls | r_nulls) | (l_nulls & r_nulls & e)
+                    };
+                    let buffer = l_nulls.zip(r_nulls).zip(e).map(c).collect();
+                    BooleanBuffer::new(buffer, 0, len)
                 }
-                _ => BooleanArray::new(values(), NullBuffer::union(Some(&l), 
Some(&r))),
+                _ => values()?,
             }
         }
         (Some(_), true, Some(a), false) | (Some(a), false, Some(_), true) => {
             // Scalar is null, other side is non-scalar and nullable
             match op {
-                Op::Distinct => a.into_inner().into(),
-                Op::NotDistinct => a.into_inner().not().into(),
-                _ => BooleanArray::new_null(len),
+                Op::Distinct => a.into_inner(),
+                Op::NotDistinct => a.into_inner().not(),
+                _ => BooleanBuffer::new_unset(len),
             }
         }
         (Some(nulls), is_scalar, None, _) | (None, _, Some(nulls), is_scalar) 
=> {
             // Only one side is nullable
             match is_scalar {
                 true => match op {
                     // Scalar is null, other side is not nullable
-                    Op::Distinct => BooleanBuffer::new_set(len).into(),
-                    Op::NotDistinct => BooleanBuffer::new_unset(len).into(),
-                    _ => BooleanArray::new_null(len),
+                    Op::Distinct => BooleanBuffer::new_set(len),
+                    Op::NotDistinct => BooleanBuffer::new_unset(len),
+                    _ => BooleanBuffer::new_unset(len),
                 },
                 false => match op {
                     Op::Distinct => {
-                        let values = values();
-                        let l = nulls.inner().bit_chunks().iter_padded();
+                        let values = values()?;
+                        let l_nulls = nulls.inner().bit_chunks().iter_padded();
                         let ne = values.bit_chunks().iter_padded();
-                        let c = |(l, n)| u64::not(l) | n;
-                        let buffer = l.zip(ne).map(c).collect();
-                        BooleanBuffer::new(buffer, 0, len).into()
+                        let c = |(l_nulls, n)| u64::not(l_nulls) | n;
+                        let buffer = l_nulls.zip(ne).map(c).collect();
+                        BooleanBuffer::new(buffer, 0, len)
                     }
-                    Op::NotDistinct => (nulls.inner() & &values()).into(),
-                    _ => BooleanArray::new(values(), Some(nulls)),
+                    Op::NotDistinct => nulls.inner() & &values()?,
+                    _ => values()?,
                 },
             }
         }
         // Neither side is nullable
-        (None, _, None, _) => BooleanArray::new(values(), None),
+        (None, _, None, _) => values()?,
     })
 }
 
+/// recursively compare fields of struct arrays
+fn compare_op_struct_values(
+    op: Op,
+    l: &dyn Array,
+    l_s: bool,
+    r: &dyn Array,
+    r_s: bool,
+    len: usize,
+) -> Result<BooleanBuffer, ArrowError> {
+    // when one of field is equal, the result is false for not equal
+    // so we use neg to reverse the result of equal when handle not equal

Review Comment:
   Why not just pass the operator into compare_op_values?



##########
arrow-ord/src/cmp.rs:
##########
@@ -215,76 +299,114 @@ fn compare_op(op: Op, lhs: &dyn Datum, rhs: &dyn Datum) 
-> Result<BooleanArray,
             (LargeBinary, LargeBinary) => apply(op, l.as_binary::<i64>(), l_s, 
l_v, r.as_binary::<i64>(), r_s, r_v),
             (FixedSizeBinary(_), FixedSizeBinary(_)) => apply(op, 
l.as_fixed_size_binary(), l_s, l_v, r.as_fixed_size_binary(), r_s, r_v),
             (Null, Null) => None,
+            (Struct(_), Struct(_)) => Some(compare_op_struct_values(op, l, 
l_s, r, r_s, len)?),
             _ => unreachable!(),
         };
-        d.unwrap_or_else(|| BooleanBuffer::new_unset(len))
+        Ok(values.unwrap_or_else(|| BooleanBuffer::new_unset(len)))
     };
-
-    let l_nulls = l_nulls.filter(|n| n.null_count() > 0);
-    let r_nulls = r_nulls.filter(|n| n.null_count() > 0);
     Ok(match (l_nulls, l_s, r_nulls, r_s) {
-        (Some(l), true, Some(r), true) | (Some(l), false, Some(r), false) => {
+        (Some(l_nulls), true, Some(r_nulls), true)
+        | (Some(l_nulls), false, Some(r_nulls), false) => {
             // Either both sides are scalar or neither side is scalar
             match op {
                 Op::Distinct => {
-                    let values = values();
-                    let l = l.inner().bit_chunks().iter_padded();
-                    let r = r.inner().bit_chunks().iter_padded();
+                    let values = values()?;
+                    let l_nulls = l_nulls.inner().bit_chunks().iter_padded();
+                    let r_nulls = r_nulls.inner().bit_chunks().iter_padded();
                     let ne = values.bit_chunks().iter_padded();
 
-                    let c = |((l, r), n)| ((l ^ r) | (l & r & n));
-                    let buffer = l.zip(r).zip(ne).map(c).collect();
-                    BooleanBuffer::new(buffer, 0, len).into()
+                    let c =
+                        |((l_nulls, r_nulls), n)| ((l_nulls ^ r_nulls) | 
(l_nulls & r_nulls & n));
+                    let buffer = l_nulls.zip(r_nulls).zip(ne).map(c).collect();
+                    BooleanBuffer::new(buffer, 0, len)
                 }
                 Op::NotDistinct => {
-                    let values = values();
-                    let l = l.inner().bit_chunks().iter_padded();
-                    let r = r.inner().bit_chunks().iter_padded();
+                    let values = values()?;
+                    let l_nulls = l_nulls.inner().bit_chunks().iter_padded();
+                    let r_nulls = r_nulls.inner().bit_chunks().iter_padded();
                     let e = values.bit_chunks().iter_padded();
 
-                    let c = |((l, r), e)| u64::not(l | r) | (l & r & e);
-                    let buffer = l.zip(r).zip(e).map(c).collect();
-                    BooleanBuffer::new(buffer, 0, len).into()
+                    let c = |((l_nulls, r_nulls), e)| {
+                        u64::not(l_nulls | r_nulls) | (l_nulls & r_nulls & e)
+                    };
+                    let buffer = l_nulls.zip(r_nulls).zip(e).map(c).collect();
+                    BooleanBuffer::new(buffer, 0, len)
                 }
-                _ => BooleanArray::new(values(), NullBuffer::union(Some(&l), 
Some(&r))),
+                _ => values()?,
             }
         }
         (Some(_), true, Some(a), false) | (Some(a), false, Some(_), true) => {
             // Scalar is null, other side is non-scalar and nullable
             match op {
-                Op::Distinct => a.into_inner().into(),
-                Op::NotDistinct => a.into_inner().not().into(),
-                _ => BooleanArray::new_null(len),
+                Op::Distinct => a.into_inner(),
+                Op::NotDistinct => a.into_inner().not(),
+                _ => BooleanBuffer::new_unset(len),
             }
         }
         (Some(nulls), is_scalar, None, _) | (None, _, Some(nulls), is_scalar) 
=> {
             // Only one side is nullable
             match is_scalar {
                 true => match op {
                     // Scalar is null, other side is not nullable
-                    Op::Distinct => BooleanBuffer::new_set(len).into(),
-                    Op::NotDistinct => BooleanBuffer::new_unset(len).into(),
-                    _ => BooleanArray::new_null(len),
+                    Op::Distinct => BooleanBuffer::new_set(len),
+                    Op::NotDistinct => BooleanBuffer::new_unset(len),
+                    _ => BooleanBuffer::new_unset(len),
                 },
                 false => match op {
                     Op::Distinct => {
-                        let values = values();
-                        let l = nulls.inner().bit_chunks().iter_padded();
+                        let values = values()?;
+                        let l_nulls = nulls.inner().bit_chunks().iter_padded();
                         let ne = values.bit_chunks().iter_padded();
-                        let c = |(l, n)| u64::not(l) | n;
-                        let buffer = l.zip(ne).map(c).collect();
-                        BooleanBuffer::new(buffer, 0, len).into()
+                        let c = |(l_nulls, n)| u64::not(l_nulls) | n;
+                        let buffer = l_nulls.zip(ne).map(c).collect();
+                        BooleanBuffer::new(buffer, 0, len)
                     }
-                    Op::NotDistinct => (nulls.inner() & &values()).into(),
-                    _ => BooleanArray::new(values(), Some(nulls)),
+                    Op::NotDistinct => nulls.inner() & &values()?,
+                    _ => values()?,
                 },
             }
         }
         // Neither side is nullable
-        (None, _, None, _) => BooleanArray::new(values(), None),
+        (None, _, None, _) => values()?,
     })
 }
 
+/// recursively compare fields of struct arrays
+fn compare_op_struct_values(
+    op: Op,
+    l: &dyn Array,
+    l_s: bool,
+    r: &dyn Array,
+    r_s: bool,
+    len: usize,
+) -> Result<BooleanBuffer, ArrowError> {
+    // when one of field is equal, the result is false for not equal
+    // so we use neg to reverse the result of equal when handle not equal
+    let neg = match op {
+        Op::Equal | Op::NotDistinct => false,
+        Op::NotEqual | Op::Distinct => true,
+        _ => unreachable!(),
+    };
+
+    let l = l.as_struct();
+    let r = r.as_struct();
+
+    // compare each field of struct
+    let child_values = l
+        .columns()
+        .iter()
+        .zip(r.columns().iter())
+        .map(|(col_l, col_r)| compare_op_values(Op::Equal, col_l, l_s, col_r, 
r_s, len))

Review Comment:
   I don't think this will correctly handle the null masks for a Distinct?



##########
arrow-ord/src/cmp.rs:
##########
@@ -702,4 +826,137 @@ mod tests {
 
         neq(&col.slice(0, col.len() - 1), &col.slice(1, col.len() - 
1)).unwrap();
     }
+
+    #[test]
+    fn test_struct_uncomparable() {
+        // test struct('a') == struct('a','b')
+        let left_a = Arc::new(Int32Array::new(
+            vec![0, 1, 2, 3].into(),
+            Some(vec![true, false, true, false].into()),
+        ));
+        let right_a = Arc::new(Int32Array::new(
+            vec![0, 1, 2, 3].into(),
+            Some(vec![true, false, true, false].into()),
+        ));
+        let right_b = Arc::new(Int32Array::new(
+            vec![0, 1, 2, 3].into(),
+            Some(vec![true, true, true, false].into()),
+        ));
+        let field_a = Arc::new(Field::new("a", DataType::Int32, true));
+        let field_b = Arc::new(Field::new("b", DataType::Int32, true));
+        let left = StructArray::from(vec![(field_a.clone(), left_a.clone() as 
ArrayRef)]);
+        let right = StructArray::from(vec![
+            (field_a.clone(), right_a.clone() as ArrayRef),
+            (field_b.clone(), right_b.clone() as ArrayRef),
+        ]);
+        assert_eq!(eq(&left, &right).unwrap_err().to_string(), "Invalid 
argument error: Invalid comparison operation: Struct([Field { name: \"a\", 
data_type: Int32, nullable: true, dict_id: 0, dict_is_ordered: false, metadata: 
{} }]) == Struct([Field { name: \"a\", data_type: Int32, nullable: true, 
dict_id: 0, dict_is_ordered: false, metadata: {} }, Field { name: \"b\", 
data_type: Int32, nullable: true, dict_id: 0, dict_is_ordered: false, metadata: 
{} }])");
+
+        // test struct('a') <= struct('a')
+        assert_eq!(lt(&left, &left).unwrap_err().to_string(), "Invalid 
argument error: Invalid comparison operation: Struct([Field { name: \"a\", 
data_type: Int32, nullable: true, dict_id: 0, dict_is_ordered: false, metadata: 
{} }]) < Struct([Field { name: \"a\", data_type: Int32, nullable: true, 
dict_id: 0, dict_is_ordered: false, metadata: {} }])");
+    }
+
+    #[test]
+    fn test_struct_compare() {
+        // test struct('a', 'b')、struct('a', 'b'), the null buffer is 0b0111
+        // left b[2] is different from right b[2]
+        let left_a = Arc::new(Int32Array::new(
+            vec![0, 1, 2, 3].into(),
+            Some(vec![true, false, true, true].into()),
+        ));
+        let right_a = Arc::new(Int32Array::new(
+            vec![0, 1, 2, 3].into(),
+            Some(vec![true, false, true, true].into()),
+        ));
+        let left_b = Arc::new(Int32Array::new(
+            vec![0, 1, 20, 3].into(),
+            Some(vec![true, true, true, true].into()),
+        ));
+        let right_b = Arc::new(Int32Array::new(
+            vec![0, 1, 2, 3].into(),
+            Some(vec![true, true, true, true].into()),
+        ));
+        let field_a = Arc::new(Field::new("a", DataType::Int32, true));
+        let field_b = Arc::new(Field::new("b", DataType::Int32, true));
+        let left_struct = StructArray::from((

Review Comment:
   ```suggestion
           // [{a: 0, b: 0}, {a: NULL, b: 1}, {a: 2, b: 20}, {a: 3, b: 3}]
           let left_struct = StructArray::from((
   ```



##########
arrow-ord/src/cmp.rs:
##########
@@ -186,10 +185,80 @@ fn compare_op(op: Op, lhs: &dyn Datum, rhs: &dyn Datum) 
-> Result<BooleanArray,
         true => r_len,
         false => l_len,
     };
+    Ok(BooleanArray::new(
+        compare_op_values(op, l, l_s, r, r_s, len)?,
+        compare_op_nulls(op, l, l_s, r, r_s, len)?,
+    ))
+}
 
-    let l_nulls = l.logical_nulls();
-    let r_nulls = r.logical_nulls();
+/// get the NullBuffer result of the comparison
+fn compare_op_nulls(
+    op: Op,
+    l: &dyn Array,
+    l_s: bool,
+    r: &dyn Array,
+    r_s: bool,
+    len: usize,
+) -> Result<Option<NullBuffer>, ArrowError> {
+    use arrow_schema::DataType::*;
+    let l_t = l.data_type();
+    let r_t = r.data_type();
+    let l_nulls = l.logical_nulls().filter(|n| n.null_count() > 0);
+    let r_nulls = r.logical_nulls().filter(|n| n.null_count() > 0);
+    // for [not]Distinct, the result is never null
+    match op {
+        Op::Distinct | Op::NotDistinct => {
+            return Ok(None);
+        }
+        _ => {}
+    }

Review Comment:
   ```suggestion
       if matches!(op, Op::Distinct | Op::NotDistinct) {
           // for [not]Distinct, the result is never null
           return Ok(None)
       }
       
       let l_t = l.data_type();
       let r_t = r.data_type();
       let l_nulls = l.logical_nulls().filter(|n| n.null_count() > 0);
       let r_nulls = r.logical_nulls().filter(|n| n.null_count() > 0);
   ```



##########
arrow-ord/src/cmp.rs:
##########
@@ -702,4 +826,137 @@ mod tests {
 
         neq(&col.slice(0, col.len() - 1), &col.slice(1, col.len() - 
1)).unwrap();
     }
+
+    #[test]
+    fn test_struct_uncomparable() {
+        // test struct('a') == struct('a','b')
+        let left_a = Arc::new(Int32Array::new(
+            vec![0, 1, 2, 3].into(),
+            Some(vec![true, false, true, false].into()),
+        ));
+        let right_a = Arc::new(Int32Array::new(
+            vec![0, 1, 2, 3].into(),
+            Some(vec![true, false, true, false].into()),
+        ));
+        let right_b = Arc::new(Int32Array::new(
+            vec![0, 1, 2, 3].into(),
+            Some(vec![true, true, true, false].into()),
+        ));
+        let field_a = Arc::new(Field::new("a", DataType::Int32, true));
+        let field_b = Arc::new(Field::new("b", DataType::Int32, true));
+        let left = StructArray::from(vec![(field_a.clone(), left_a.clone() as 
ArrayRef)]);
+        let right = StructArray::from(vec![
+            (field_a.clone(), right_a.clone() as ArrayRef),
+            (field_b.clone(), right_b.clone() as ArrayRef),
+        ]);
+        assert_eq!(eq(&left, &right).unwrap_err().to_string(), "Invalid 
argument error: Invalid comparison operation: Struct([Field { name: \"a\", 
data_type: Int32, nullable: true, dict_id: 0, dict_is_ordered: false, metadata: 
{} }]) == Struct([Field { name: \"a\", data_type: Int32, nullable: true, 
dict_id: 0, dict_is_ordered: false, metadata: {} }, Field { name: \"b\", 
data_type: Int32, nullable: true, dict_id: 0, dict_is_ordered: false, metadata: 
{} }])");
+
+        // test struct('a') <= struct('a')
+        assert_eq!(lt(&left, &left).unwrap_err().to_string(), "Invalid 
argument error: Invalid comparison operation: Struct([Field { name: \"a\", 
data_type: Int32, nullable: true, dict_id: 0, dict_is_ordered: false, metadata: 
{} }]) < Struct([Field { name: \"a\", data_type: Int32, nullable: true, 
dict_id: 0, dict_is_ordered: false, metadata: {} }])");
+    }
+
+    #[test]
+    fn test_struct_compare() {
+        // test struct('a', 'b')、struct('a', 'b'), the null buffer is 0b0111
+        // left b[2] is different from right b[2]
+        let left_a = Arc::new(Int32Array::new(
+            vec![0, 1, 2, 3].into(),
+            Some(vec![true, false, true, true].into()),
+        ));
+        let right_a = Arc::new(Int32Array::new(
+            vec![0, 1, 2, 3].into(),

Review Comment:
   ```suggestion
               vec![0, 72, 2, 3].into(),
   ```
   
   This helps verify the null mask comparison is correct



##########
arrow-ord/src/cmp.rs:
##########
@@ -702,4 +826,137 @@ mod tests {
 
         neq(&col.slice(0, col.len() - 1), &col.slice(1, col.len() - 
1)).unwrap();
     }
+
+    #[test]
+    fn test_struct_uncomparable() {
+        // test struct('a') == struct('a','b')
+        let left_a = Arc::new(Int32Array::new(
+            vec![0, 1, 2, 3].into(),
+            Some(vec![true, false, true, false].into()),
+        ));
+        let right_a = Arc::new(Int32Array::new(
+            vec![0, 1, 2, 3].into(),
+            Some(vec![true, false, true, false].into()),
+        ));
+        let right_b = Arc::new(Int32Array::new(
+            vec![0, 1, 2, 3].into(),
+            Some(vec![true, true, true, false].into()),
+        ));
+        let field_a = Arc::new(Field::new("a", DataType::Int32, true));
+        let field_b = Arc::new(Field::new("b", DataType::Int32, true));
+        let left = StructArray::from(vec![(field_a.clone(), left_a.clone() as 
ArrayRef)]);
+        let right = StructArray::from(vec![
+            (field_a.clone(), right_a.clone() as ArrayRef),
+            (field_b.clone(), right_b.clone() as ArrayRef),
+        ]);
+        assert_eq!(eq(&left, &right).unwrap_err().to_string(), "Invalid 
argument error: Invalid comparison operation: Struct([Field { name: \"a\", 
data_type: Int32, nullable: true, dict_id: 0, dict_is_ordered: false, metadata: 
{} }]) == Struct([Field { name: \"a\", data_type: Int32, nullable: true, 
dict_id: 0, dict_is_ordered: false, metadata: {} }, Field { name: \"b\", 
data_type: Int32, nullable: true, dict_id: 0, dict_is_ordered: false, metadata: 
{} }])");
+
+        // test struct('a') <= struct('a')
+        assert_eq!(lt(&left, &left).unwrap_err().to_string(), "Invalid 
argument error: Invalid comparison operation: Struct([Field { name: \"a\", 
data_type: Int32, nullable: true, dict_id: 0, dict_is_ordered: false, metadata: 
{} }]) < Struct([Field { name: \"a\", data_type: Int32, nullable: true, 
dict_id: 0, dict_is_ordered: false, metadata: {} }])");
+    }
+
+    #[test]
+    fn test_struct_compare() {
+        // test struct('a', 'b')、struct('a', 'b'), the null buffer is 0b0111
+        // left b[2] is different from right b[2]
+        let left_a = Arc::new(Int32Array::new(
+            vec![0, 1, 2, 3].into(),
+            Some(vec![true, false, true, true].into()),
+        ));
+        let right_a = Arc::new(Int32Array::new(
+            vec![0, 1, 2, 3].into(),
+            Some(vec![true, false, true, true].into()),
+        ));
+        let left_b = Arc::new(Int32Array::new(
+            vec![0, 1, 20, 3].into(),
+            Some(vec![true, true, true, true].into()),
+        ));
+        let right_b = Arc::new(Int32Array::new(
+            vec![0, 1, 2, 3].into(),
+            Some(vec![true, true, true, true].into()),
+        ));
+        let field_a = Arc::new(Field::new("a", DataType::Int32, true));
+        let field_b = Arc::new(Field::new("b", DataType::Int32, true));
+        let left_struct = StructArray::from((
+            vec![
+                (field_a.clone(), left_a.clone() as ArrayRef),
+                (field_b.clone(), left_b.clone() as ArrayRef),
+            ],
+            Buffer::from([0b0111]),
+        ));
+        let right_struct = StructArray::from((

Review Comment:
   ```suggestion
           // right [{a: 0, b: 0}, {a: NULL, b: 1}, {a: 2, b: 2}, {a: 3, b: 3} ]
           let right_struct = StructArray::from((
   ```



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

Reply via email to