| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| use crate::array::print_long_array; |
| use crate::builder::BooleanBuilder; |
| use crate::iterator::BooleanIter; |
| use crate::{Array, ArrayAccessor, ArrayRef, Scalar}; |
| use arrow_buffer::bit_chunk_iterator::UnalignedBitChunk; |
| use arrow_buffer::{BooleanBuffer, Buffer, MutableBuffer, NullBuffer, bit_util}; |
| use arrow_data::{ArrayData, ArrayDataBuilder}; |
| use arrow_schema::DataType; |
| use std::any::Any; |
| use std::sync::Arc; |
| |
| /// An array of [boolean values](https://arrow.apache.org/docs/format/Columnar.html#fixed-size-primitive-layout) |
| /// |
| /// # Example: From a Vec |
| /// |
| /// ``` |
| /// # use arrow_array::{Array, BooleanArray}; |
| /// let arr: BooleanArray = vec![true, true, false].into(); |
| /// ``` |
| /// |
| /// # Example: From an optional Vec |
| /// |
| /// ``` |
| /// # use arrow_array::{Array, BooleanArray}; |
| /// let arr: BooleanArray = vec![Some(true), None, Some(false)].into(); |
| /// ``` |
| /// |
| /// # Example: From an iterator |
| /// |
| /// ``` |
| /// # use arrow_array::{Array, BooleanArray}; |
| /// let arr: BooleanArray = (0..5).map(|x| (x % 2 == 0).then(|| x % 3 == 0)).collect(); |
| /// let values: Vec<_> = arr.iter().collect(); |
| /// assert_eq!(&values, &[Some(true), None, Some(false), None, Some(false)]) |
| /// ``` |
| /// |
| /// # Example: Using Builder |
| /// |
| /// ``` |
| /// # use arrow_array::Array; |
| /// # use arrow_array::builder::BooleanBuilder; |
| /// let mut builder = BooleanBuilder::new(); |
| /// builder.append_value(true); |
| /// builder.append_null(); |
| /// builder.append_value(false); |
| /// let array = builder.finish(); |
| /// let values: Vec<_> = array.iter().collect(); |
| /// assert_eq!(&values, &[Some(true), None, Some(false)]) |
| /// ``` |
| /// |
| #[derive(Clone)] |
| pub struct BooleanArray { |
| values: BooleanBuffer, |
| nulls: Option<NullBuffer>, |
| } |
| |
| impl std::fmt::Debug for BooleanArray { |
| fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |
| write!(f, "BooleanArray\n[\n")?; |
| print_long_array(self, f, |array, index, f| { |
| std::fmt::Debug::fmt(&array.value(index), f) |
| })?; |
| write!(f, "]") |
| } |
| } |
| |
| impl BooleanArray { |
| /// Create a new [`BooleanArray`] from the provided values and nulls |
| /// |
| /// # Panics |
| /// |
| /// Panics if `values.len() != nulls.len()` |
| pub fn new(values: BooleanBuffer, nulls: Option<NullBuffer>) -> Self { |
| if let Some(n) = nulls.as_ref() { |
| assert_eq!(values.len(), n.len()); |
| } |
| Self { values, nulls } |
| } |
| |
| /// Create a new [`BooleanArray`] with length `len` consisting only of nulls |
| pub fn new_null(len: usize) -> Self { |
| Self { |
| values: BooleanBuffer::new_unset(len), |
| nulls: Some(NullBuffer::new_null(len)), |
| } |
| } |
| |
| /// Create a new [`Scalar`] from `value` |
| pub fn new_scalar(value: bool) -> Scalar<Self> { |
| let values = match value { |
| true => BooleanBuffer::new_set(1), |
| false => BooleanBuffer::new_unset(1), |
| }; |
| Scalar::new(Self::new(values, None)) |
| } |
| |
| /// Create a new [`BooleanArray`] from a [`Buffer`] specified by `offset` and `len`, the `offset` and `len` in bits |
| /// Logically convert each bit in [`Buffer`] to boolean and use it to build [`BooleanArray`]. |
| /// using this method will make the following points self-evident: |
| /// * there is no `null` in the constructed [`BooleanArray`]; |
| /// * without considering `buffer.into()`, this method is efficient because there is no need to perform pack and unpack operations on boolean; |
| pub fn new_from_packed(buffer: impl Into<Buffer>, offset: usize, len: usize) -> Self { |
| BooleanBuffer::new(buffer.into(), offset, len).into() |
| } |
| |
| /// Create a new [`BooleanArray`] from `&[u8]` |
| /// This method uses `new_from_packed` and constructs a [`Buffer`] using `value`, and offset is set to 0 and len is set to `value.len() * 8` |
| /// using this method will make the following points self-evident: |
| /// * there is no `null` in the constructed [`BooleanArray`]; |
| /// * the length of the constructed [`BooleanArray`] is always a multiple of 8; |
| pub fn new_from_u8(value: &[u8]) -> Self { |
| BooleanBuffer::new(Buffer::from(value), 0, value.len() * 8).into() |
| } |
| |
| /// Returns the length of this array. |
| pub fn len(&self) -> usize { |
| self.values.len() |
| } |
| |
| /// Returns whether this array is empty. |
| pub fn is_empty(&self) -> bool { |
| self.values.is_empty() |
| } |
| |
| /// Returns a zero-copy slice of this array with the indicated offset and length. |
| pub fn slice(&self, offset: usize, length: usize) -> Self { |
| Self { |
| values: self.values.slice(offset, length), |
| nulls: self.nulls.as_ref().map(|n| n.slice(offset, length)), |
| } |
| } |
| |
| /// Returns a new boolean array builder |
| pub fn builder(capacity: usize) -> BooleanBuilder { |
| BooleanBuilder::with_capacity(capacity) |
| } |
| |
| /// Returns the underlying [`BooleanBuffer`] holding all the values of this array |
| pub fn values(&self) -> &BooleanBuffer { |
| &self.values |
| } |
| |
| /// Block size for chunked fold operations in [`Self::has_true`] and [`Self::has_false`]. |
| /// Using `chunks_exact` with this size lets the compiler fully unroll the inner |
| /// fold (no inner branch/loop), enabling short-circuit exits every N chunks. |
| const CHUNK_FOLD_BLOCK_SIZE: usize = 16; |
| |
| /// Returns an [`UnalignedBitChunk`] over this array's values. |
| fn unaligned_bit_chunks(&self) -> UnalignedBitChunk<'_> { |
| UnalignedBitChunk::new(self.values().values(), self.values().offset(), self.len()) |
| } |
| |
| /// Returns the number of non null, true values within this array. |
| /// If you only need to check if there is at least one true value, consider using `has_true()` which can short-circuit and be more efficient. |
| pub fn true_count(&self) -> usize { |
| match self.nulls() { |
| Some(nulls) => { |
| let null_chunks = nulls.inner().bit_chunks().iter_padded(); |
| let value_chunks = self.values().bit_chunks().iter_padded(); |
| null_chunks |
| .zip(value_chunks) |
| .map(|(a, b)| (a & b).count_ones() as usize) |
| .sum() |
| } |
| None => self.values().count_set_bits(), |
| } |
| } |
| |
| /// Returns the number of non null, false values within this array. |
| /// If you only need to check if there is at least one false value, consider using `has_false()` which can short-circuit and be more efficient. |
| pub fn false_count(&self) -> usize { |
| self.len() - self.null_count() - self.true_count() |
| } |
| |
| /// Returns whether there is at least one non-null `true` value in this array. |
| /// |
| /// This is more efficient than `true_count() > 0` because it can short-circuit |
| /// as soon as a `true` value is found, without counting all set bits. |
| /// |
| /// Null values are not counted as `true`. Returns `false` for empty arrays. |
| pub fn has_true(&self) -> bool { |
| match self.nulls() { |
| Some(nulls) => { |
| let null_chunks = nulls.inner().bit_chunks().iter_padded(); |
| let value_chunks = self.values().bit_chunks().iter_padded(); |
| null_chunks.zip(value_chunks).any(|(n, v)| (n & v) != 0) |
| } |
| None => { |
| let bit_chunks = self.unaligned_bit_chunks(); |
| let chunks = bit_chunks.chunks(); |
| let mut exact = chunks.chunks_exact(Self::CHUNK_FOLD_BLOCK_SIZE); |
| let found = bit_chunks.prefix().unwrap_or(0) != 0 |
| || exact.any(|block| block.iter().fold(0u64, |acc, &c| acc | c) != 0); |
| found |
| || exact.remainder().iter().any(|&c| c != 0) |
| || bit_chunks.suffix().unwrap_or(0) != 0 |
| } |
| } |
| } |
| |
| /// Returns whether there is at least one non-null `false` value in this array. |
| /// |
| /// This is more efficient than `false_count() > 0` because it can short-circuit |
| /// as soon as a `false` value is found, without counting all set bits. |
| /// |
| /// Null values are not counted as `false`. Returns `false` for empty arrays. |
| pub fn has_false(&self) -> bool { |
| match self.nulls() { |
| Some(nulls) => { |
| let null_chunks = nulls.inner().bit_chunks().iter_padded(); |
| let value_chunks = self.values().bit_chunks().iter_padded(); |
| null_chunks.zip(value_chunks).any(|(n, v)| (n & !v) != 0) |
| } |
| None => { |
| let bit_chunks = self.unaligned_bit_chunks(); |
| // UnalignedBitChunk zeros padding bits; fill them with 1s so |
| // they don't appear as false values. |
| let lead_mask = !((1u64 << bit_chunks.lead_padding()) - 1); |
| let trail_mask = if bit_chunks.trailing_padding() == 0 { |
| u64::MAX |
| } else { |
| (1u64 << (64 - bit_chunks.trailing_padding())) - 1 |
| }; |
| let (prefix_fill, suffix_fill) = match (bit_chunks.prefix(), bit_chunks.suffix()) { |
| (Some(_), Some(_)) => (!lead_mask, !trail_mask), |
| (Some(_), None) => (!lead_mask | !trail_mask, 0), |
| (None, Some(_)) => (0, !trail_mask), |
| (None, None) => (0, 0), |
| }; |
| let chunks = bit_chunks.chunks(); |
| let mut exact = chunks.chunks_exact(Self::CHUNK_FOLD_BLOCK_SIZE); |
| let found = bit_chunks |
| .prefix() |
| .is_some_and(|v| (v | prefix_fill) != u64::MAX) |
| || exact |
| .any(|block| block.iter().fold(u64::MAX, |acc, &c| acc & c) != u64::MAX); |
| found |
| || exact.remainder().iter().any(|&c| c != u64::MAX) |
| || bit_chunks |
| .suffix() |
| .is_some_and(|v| (v | suffix_fill) != u64::MAX) |
| } |
| } |
| } |
| |
| /// Returns the boolean value at index `i`. |
| /// |
| /// Note: This method does not check for nulls and the value is arbitrary |
| /// if [`is_null`](Self::is_null) returns true for the index. |
| /// |
| /// # Safety |
| /// This doesn't check bounds, the caller must ensure that index < self.len() |
| pub unsafe fn value_unchecked(&self, i: usize) -> bool { |
| unsafe { self.values.value_unchecked(i) } |
| } |
| |
| /// Returns the boolean value at index `i`. |
| /// |
| /// Note: This method does not check for nulls and the value is arbitrary |
| /// if [`is_null`](Self::is_null) returns true for the index. |
| /// |
| /// # Panics |
| /// Panics if index `i` is out of bounds |
| pub fn value(&self, i: usize) -> bool { |
| assert!( |
| i < self.len(), |
| "Trying to access an element at index {} from a BooleanArray of length {}", |
| i, |
| self.len() |
| ); |
| // Safety: |
| // `i < self.len() |
| unsafe { self.value_unchecked(i) } |
| } |
| |
| /// Returns an iterator that returns the values of `array.value(i)` for an iterator with each element `i` |
| pub fn take_iter<'a>( |
| &'a self, |
| indexes: impl Iterator<Item = Option<usize>> + 'a, |
| ) -> impl Iterator<Item = Option<bool>> + 'a { |
| indexes.map(|opt_index| opt_index.map(|index| self.value(index))) |
| } |
| |
| /// Returns an iterator that returns the values of `array.value(i)` for an iterator with each element `i` |
| /// # Safety |
| /// |
| /// caller must ensure that the offsets in the iterator are less than the array len() |
| pub unsafe fn take_iter_unchecked<'a>( |
| &'a self, |
| indexes: impl Iterator<Item = Option<usize>> + 'a, |
| ) -> impl Iterator<Item = Option<bool>> + 'a { |
| indexes.map(|opt_index| opt_index.map(|index| unsafe { self.value_unchecked(index) })) |
| } |
| |
| /// Create a [`BooleanArray`] by evaluating the operation for |
| /// each element of the provided array |
| /// |
| /// ``` |
| /// # use arrow_array::{BooleanArray, Int32Array}; |
| /// |
| /// let array = Int32Array::from(vec![1, 2, 3, 4, 5]); |
| /// let r = BooleanArray::from_unary(&array, |x| x > 2); |
| /// assert_eq!(&r, &BooleanArray::from(vec![false, false, true, true, true])); |
| /// ``` |
| pub fn from_unary<T: ArrayAccessor, F>(left: T, mut op: F) -> Self |
| where |
| F: FnMut(T::Item) -> bool, |
| { |
| let nulls = left.logical_nulls(); |
| let values = BooleanBuffer::collect_bool(left.len(), |i| unsafe { |
| // SAFETY: i in range 0..len |
| op(left.value_unchecked(i)) |
| }); |
| Self::new(values, nulls) |
| } |
| |
| /// Create a [`BooleanArray`] by evaluating the binary operation for |
| /// each element of the provided arrays |
| /// |
| /// ``` |
| /// # use arrow_array::{BooleanArray, Int32Array}; |
| /// |
| /// let a = Int32Array::from(vec![1, 2, 3, 4, 5]); |
| /// let b = Int32Array::from(vec![1, 2, 0, 2, 5]); |
| /// let r = BooleanArray::from_binary(&a, &b, |a, b| a == b); |
| /// assert_eq!(&r, &BooleanArray::from(vec![true, true, false, false, true])); |
| /// ``` |
| /// |
| /// # Panics |
| /// |
| /// This function panics if left and right are not the same length |
| /// |
| pub fn from_binary<T: ArrayAccessor, S: ArrayAccessor, F>(left: T, right: S, mut op: F) -> Self |
| where |
| F: FnMut(T::Item, S::Item) -> bool, |
| { |
| assert_eq!(left.len(), right.len()); |
| |
| let nulls = NullBuffer::union( |
| left.logical_nulls().as_ref(), |
| right.logical_nulls().as_ref(), |
| ); |
| let values = BooleanBuffer::collect_bool(left.len(), |i| unsafe { |
| // SAFETY: i in range 0..len |
| op(left.value_unchecked(i), right.value_unchecked(i)) |
| }); |
| Self::new(values, nulls) |
| } |
| |
| /// Apply a bitwise operation to this array's values using u64 operations, |
| /// returning a new [`BooleanArray`]. |
| /// |
| /// The null buffer is preserved unchanged. |
| /// |
| /// See [`BooleanBuffer::from_bitwise_unary_op`] for details on the operation. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// # use arrow_array::BooleanArray; |
| /// let array = BooleanArray::from(vec![true, false, true]); |
| /// let result = array.bitwise_unary(|x| !x); |
| /// assert_eq!(result, BooleanArray::from(vec![false, true, false])); |
| /// ``` |
| pub fn bitwise_unary<F>(&self, op: F) -> BooleanArray |
| where |
| F: FnMut(u64) -> u64, |
| { |
| let values = BooleanBuffer::from_bitwise_unary_op( |
| self.values.values(), |
| self.values.offset(), |
| self.values.len(), |
| op, |
| ); |
| BooleanArray::new(values, self.nulls.clone()) |
| } |
| |
| /// Try to apply a bitwise operation to this array's values in place using |
| /// u64 operations. |
| /// |
| /// If the underlying buffer is uniquely owned, the operation is applied |
| /// in place and `Ok` is returned. If the buffer is shared, `Err(self)` is |
| /// returned so the caller can fall back to [`bitwise_unary`](Self::bitwise_unary). |
| /// |
| /// The null buffer is preserved unchanged. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// # use arrow_array::BooleanArray; |
| /// let array = BooleanArray::from(vec![true, false, true]); |
| /// let result = array.bitwise_unary_mut(|x| !x).unwrap(); |
| /// assert_eq!(result, BooleanArray::from(vec![false, true, false])); |
| /// ``` |
| pub fn bitwise_unary_mut<F>(self, op: F) -> Result<BooleanArray, BooleanArray> |
| where |
| F: FnMut(u64) -> u64, |
| { |
| self.try_bitwise_unary_in_place(op) |
| .map_err(|(array, _op)| array) |
| } |
| |
| /// Apply a bitwise operation to this array's values in place if the buffer |
| /// is uniquely owned, or clone and apply if shared. |
| /// |
| /// This is a convenience wrapper around [`bitwise_unary_mut`](Self::bitwise_unary_mut) |
| /// that falls back to [`bitwise_unary`](Self::bitwise_unary) when the buffer is shared. |
| /// |
| /// The null buffer is preserved unchanged. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// # use arrow_array::BooleanArray; |
| /// let array = BooleanArray::from(vec![true, false, true]); |
| /// let result = array.bitwise_unary_mut_or_clone(|x| !x); |
| /// assert_eq!(result, BooleanArray::from(vec![false, true, false])); |
| /// ``` |
| pub fn bitwise_unary_mut_or_clone<F>(self, op: F) -> BooleanArray |
| where |
| F: FnMut(u64) -> u64, |
| { |
| match self.try_bitwise_unary_in_place(op) { |
| Ok(array) => array, |
| Err((array, op)) => array.bitwise_unary(op), |
| } |
| } |
| |
| /// Try to apply a unary op in place. Returns `op` back on failure so |
| /// callers can fall back to an allocating path without requiring `F: Clone`. |
| fn try_bitwise_unary_in_place<F>(self, op: F) -> Result<BooleanArray, (BooleanArray, F)> |
| where |
| F: FnMut(u64) -> u64, |
| { |
| let (values, nulls) = self.into_parts(); |
| let offset = values.offset(); |
| let len = values.len(); |
| let buffer = values.into_inner(); |
| match buffer.into_mutable() { |
| Ok(mut buf) => { |
| bit_util::apply_bitwise_unary_op(buf.as_slice_mut(), offset, len, op); |
| let values = BooleanBuffer::new(buf.into(), offset, len); |
| Ok(BooleanArray::new(values, nulls)) |
| } |
| Err(buffer) => { |
| let values = BooleanBuffer::new(buffer, offset, len); |
| Err((BooleanArray::new(values, nulls), op)) |
| } |
| } |
| } |
| |
| /// Apply a bitwise binary operation to this array and `rhs` using u64 |
| /// operations, returning a new [`BooleanArray`]. |
| /// |
| /// Null buffers are unioned: the result is null where either input is null. |
| /// |
| /// See [`BooleanBuffer::from_bitwise_binary_op`] for details on the operation. |
| /// |
| /// # Panics |
| /// |
| /// Panics if `self` and `rhs` have different lengths. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// # use arrow_array::BooleanArray; |
| /// let a = BooleanArray::from(vec![true, false, true, true]); |
| /// let b = BooleanArray::from(vec![true, true, false, true]); |
| /// let result = a.bitwise_bin_op(&b, |a, b| a & b); |
| /// assert_eq!(result, BooleanArray::from(vec![true, false, false, true])); |
| /// ``` |
| pub fn bitwise_bin_op<F>(&self, rhs: &BooleanArray, op: F) -> BooleanArray |
| where |
| F: FnMut(u64, u64) -> u64, |
| { |
| assert_eq!(self.len(), rhs.len()); |
| let nulls = NullBuffer::union(self.nulls(), rhs.nulls()); |
| let values = BooleanBuffer::from_bitwise_binary_op( |
| self.values.values(), |
| self.values.offset(), |
| rhs.values.values(), |
| rhs.values.offset(), |
| self.values.len(), |
| op, |
| ); |
| BooleanArray::new(values, nulls) |
| } |
| |
| /// Try to apply a bitwise binary operation to this array and `rhs` in |
| /// place using u64 operations. |
| /// |
| /// If this array's underlying buffer is uniquely owned, the operation is |
| /// applied in place and `Ok` is returned. If the buffer is shared, |
| /// `Err(self)` is returned so the caller can fall back to |
| /// [`bitwise_bin_op`](Self::bitwise_bin_op). |
| /// |
| /// Null buffers are unioned: the result is null where either input is null. |
| /// |
| /// # Panics |
| /// |
| /// Panics if `self` and `rhs` have different lengths. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// # use arrow_array::BooleanArray; |
| /// let a = BooleanArray::from(vec![true, false, true, true]); |
| /// let b = BooleanArray::from(vec![true, true, false, true]); |
| /// let result = a.bitwise_bin_op_mut(&b, |a, b| a & b).unwrap(); |
| /// assert_eq!(result, BooleanArray::from(vec![true, false, false, true])); |
| /// ``` |
| pub fn bitwise_bin_op_mut<F>( |
| self, |
| rhs: &BooleanArray, |
| op: F, |
| ) -> Result<BooleanArray, BooleanArray> |
| where |
| F: FnMut(u64, u64) -> u64, |
| { |
| self.try_bitwise_bin_op_in_place(rhs, op) |
| .map_err(|(array, _op)| array) |
| } |
| |
| /// Apply a bitwise binary operation to this array and `rhs` in place if the |
| /// buffer is uniquely owned, or clone and apply if shared. |
| /// |
| /// This is a convenience wrapper around [`bitwise_bin_op_mut`](Self::bitwise_bin_op_mut) |
| /// that falls back to [`bitwise_bin_op`](Self::bitwise_bin_op) when the buffer is shared. |
| /// |
| /// Null buffers are unioned: the result is null where either input is null. |
| /// |
| /// # Panics |
| /// |
| /// Panics if `self` and `rhs` have different lengths. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// # use arrow_array::BooleanArray; |
| /// let a = BooleanArray::from(vec![true, false, true, true]); |
| /// let b = BooleanArray::from(vec![true, true, false, true]); |
| /// let result = a.bitwise_bin_op_mut_or_clone(&b, |a, b| a & b); |
| /// assert_eq!(result, BooleanArray::from(vec![true, false, false, true])); |
| /// ``` |
| pub fn bitwise_bin_op_mut_or_clone<F>(self, rhs: &BooleanArray, op: F) -> BooleanArray |
| where |
| F: FnMut(u64, u64) -> u64, |
| { |
| match self.try_bitwise_bin_op_in_place(rhs, op) { |
| Ok(array) => array, |
| Err((array, op)) => array.bitwise_bin_op(rhs, op), |
| } |
| } |
| |
| /// Try to apply a binary op in place. Returns `op` back on failure so |
| /// callers can fall back to an allocating path without requiring `F: Clone`. |
| fn try_bitwise_bin_op_in_place<F>( |
| self, |
| rhs: &BooleanArray, |
| op: F, |
| ) -> Result<BooleanArray, (BooleanArray, F)> |
| where |
| F: FnMut(u64, u64) -> u64, |
| { |
| assert_eq!(self.len(), rhs.len()); |
| let (values, nulls) = self.into_parts(); |
| let offset = values.offset(); |
| let len = values.len(); |
| let buffer = values.into_inner(); |
| match buffer.into_mutable() { |
| Ok(mut buf) => { |
| bit_util::apply_bitwise_binary_op( |
| buf.as_slice_mut(), |
| offset, |
| rhs.values.inner(), |
| rhs.values.offset(), |
| len, |
| op, |
| ); |
| // Defer null union to the success path so the Err path returns |
| // self's original nulls, avoiding a redundant union in callers |
| // that fall back to bitwise_bin_op. |
| let nulls = NullBuffer::union(nulls.as_ref(), rhs.nulls()); |
| let values = BooleanBuffer::new(buf.into(), offset, len); |
| Ok(BooleanArray::new(values, nulls)) |
| } |
| Err(buffer) => { |
| let values = BooleanBuffer::new(buffer, offset, len); |
| Err((BooleanArray::new(values, nulls), op)) |
| } |
| } |
| } |
| |
| /// Deconstruct this array into its constituent parts |
| pub fn into_parts(self) -> (BooleanBuffer, Option<NullBuffer>) { |
| (self.values, self.nulls) |
| } |
| } |
| |
| /// SAFETY: Correctly implements the contract of Arrow Arrays |
| unsafe impl Array for BooleanArray { |
| fn as_any(&self) -> &dyn Any { |
| self |
| } |
| |
| fn to_data(&self) -> ArrayData { |
| self.clone().into() |
| } |
| |
| fn into_data(self) -> ArrayData { |
| self.into() |
| } |
| |
| fn data_type(&self) -> &DataType { |
| &DataType::Boolean |
| } |
| |
| fn slice(&self, offset: usize, length: usize) -> ArrayRef { |
| Arc::new(self.slice(offset, length)) |
| } |
| |
| fn len(&self) -> usize { |
| self.values.len() |
| } |
| |
| fn is_empty(&self) -> bool { |
| self.values.is_empty() |
| } |
| |
| fn shrink_to_fit(&mut self) { |
| self.values.shrink_to_fit(); |
| if let Some(nulls) = &mut self.nulls { |
| nulls.shrink_to_fit(); |
| } |
| } |
| |
| fn offset(&self) -> usize { |
| self.values.offset() |
| } |
| |
| fn nulls(&self) -> Option<&NullBuffer> { |
| self.nulls.as_ref() |
| } |
| |
| fn logical_null_count(&self) -> usize { |
| self.null_count() |
| } |
| |
| fn get_buffer_memory_size(&self) -> usize { |
| let mut sum = self.values.inner().capacity(); |
| if let Some(x) = &self.nulls { |
| sum += x.buffer().capacity() |
| } |
| sum |
| } |
| |
| fn get_array_memory_size(&self) -> usize { |
| std::mem::size_of::<Self>() + self.get_buffer_memory_size() |
| } |
| |
| #[cfg(feature = "pool")] |
| fn claim(&self, pool: &dyn arrow_buffer::MemoryPool) { |
| self.values.claim(pool); |
| if let Some(nulls) = &self.nulls { |
| nulls.claim(pool); |
| } |
| } |
| } |
| |
| impl ArrayAccessor for &BooleanArray { |
| type Item = bool; |
| |
| fn value(&self, index: usize) -> Self::Item { |
| BooleanArray::value(self, index) |
| } |
| |
| unsafe fn value_unchecked(&self, index: usize) -> Self::Item { |
| unsafe { BooleanArray::value_unchecked(self, index) } |
| } |
| } |
| |
| impl From<Vec<bool>> for BooleanArray { |
| fn from(data: Vec<bool>) -> Self { |
| let mut mut_buf = MutableBuffer::new_null(data.len()); |
| { |
| let mut_slice = mut_buf.as_slice_mut(); |
| for (i, b) in data.iter().enumerate() { |
| if *b { |
| bit_util::set_bit(mut_slice, i); |
| } |
| } |
| } |
| let array_data = ArrayData::builder(DataType::Boolean) |
| .len(data.len()) |
| .add_buffer(mut_buf.into()); |
| |
| let array_data = unsafe { array_data.build_unchecked() }; |
| BooleanArray::from(array_data) |
| } |
| } |
| |
| impl From<Vec<Option<bool>>> for BooleanArray { |
| fn from(data: Vec<Option<bool>>) -> Self { |
| data.iter().collect() |
| } |
| } |
| |
| impl From<ArrayData> for BooleanArray { |
| fn from(data: ArrayData) -> Self { |
| let (data_type, len, nulls, offset, mut buffers, _child_data) = data.into_parts(); |
| assert_eq!( |
| data_type, |
| DataType::Boolean, |
| "BooleanArray expected ArrayData with type Boolean got {data_type:?}", |
| ); |
| assert_eq!( |
| buffers.len(), |
| 1, |
| "BooleanArray data should contain a single buffer only (values buffer)" |
| ); |
| let buffer = buffers.pop().expect("checked above"); |
| let values = BooleanBuffer::new(buffer, offset, len); |
| |
| Self { values, nulls } |
| } |
| } |
| |
| impl From<BooleanArray> for ArrayData { |
| fn from(array: BooleanArray) -> Self { |
| let builder = ArrayDataBuilder::new(DataType::Boolean) |
| .len(array.values.len()) |
| .offset(array.values.offset()) |
| .nulls(array.nulls) |
| .buffers(vec![array.values.into_inner()]); |
| |
| unsafe { builder.build_unchecked() } |
| } |
| } |
| |
| impl<'a> IntoIterator for &'a BooleanArray { |
| type Item = Option<bool>; |
| type IntoIter = BooleanIter<'a>; |
| |
| fn into_iter(self) -> Self::IntoIter { |
| BooleanIter::<'a>::new(self) |
| } |
| } |
| |
| impl<'a> BooleanArray { |
| /// constructs a new iterator |
| pub fn iter(&'a self) -> BooleanIter<'a> { |
| BooleanIter::<'a>::new(self) |
| } |
| } |
| |
| /// An optional boolean value |
| /// |
| /// This struct is used as an adapter when creating `BooleanArray` from an iterator. |
| /// `FromIterator` for `BooleanArray` takes an iterator where the elements can be `into` |
| /// this struct. So once implementing `From` or `Into` trait for a type, an iterator of |
| /// the type can be collected to `BooleanArray`. |
| /// |
| /// See also [NativeAdapter](crate::array::NativeAdapter). |
| #[derive(Debug)] |
| struct BooleanAdapter { |
| /// Corresponding Rust native type if available |
| pub native: Option<bool>, |
| } |
| |
| impl From<bool> for BooleanAdapter { |
| fn from(value: bool) -> Self { |
| BooleanAdapter { |
| native: Some(value), |
| } |
| } |
| } |
| |
| impl From<&bool> for BooleanAdapter { |
| fn from(value: &bool) -> Self { |
| BooleanAdapter { |
| native: Some(*value), |
| } |
| } |
| } |
| |
| impl From<Option<bool>> for BooleanAdapter { |
| fn from(value: Option<bool>) -> Self { |
| BooleanAdapter { native: value } |
| } |
| } |
| |
| impl From<&Option<bool>> for BooleanAdapter { |
| fn from(value: &Option<bool>) -> Self { |
| BooleanAdapter { native: *value } |
| } |
| } |
| |
| impl<Ptr: Into<BooleanAdapter>> FromIterator<Ptr> for BooleanArray { |
| fn from_iter<I: IntoIterator<Item = Ptr>>(iter: I) -> Self { |
| let iter = iter.into_iter(); |
| let capacity = match iter.size_hint() { |
| (lower, Some(upper)) if lower == upper => lower, |
| _ => 0, |
| }; |
| let mut builder = BooleanBuilder::with_capacity(capacity); |
| builder.extend(iter.map(|item| item.into().native)); |
| builder.finish() |
| } |
| } |
| |
| impl BooleanArray { |
| /// Creates a [`BooleanArray`] from an iterator of trusted length. |
| /// |
| /// # Safety |
| /// |
| /// The iterator must be [`TrustedLen`](https://doc.rust-lang.org/std/iter/trait.TrustedLen.html). |
| /// I.e. that `size_hint().1` correctly reports its length. Note that this is a stronger |
| /// guarantee that `ExactSizeIterator` provides which could still report a wrong length. |
| /// |
| /// # Panics |
| /// |
| /// Panics if the iterator does not report an upper bound on `size_hint()`. |
| #[inline] |
| #[allow( |
| private_bounds, |
| reason = "We will expose BooleanAdapter if there is a need" |
| )] |
| pub unsafe fn from_trusted_len_iter<I, P>(iter: I) -> Self |
| where |
| P: Into<BooleanAdapter>, |
| I: ExactSizeIterator<Item = P>, |
| { |
| let data_len = iter.len(); |
| |
| let num_bytes = bit_util::ceil(data_len, 8); |
| let mut null_builder = MutableBuffer::from_len_zeroed(num_bytes); |
| let mut val_builder = MutableBuffer::from_len_zeroed(num_bytes); |
| |
| let data = val_builder.as_slice_mut(); |
| |
| let null_slice = null_builder.as_slice_mut(); |
| iter.enumerate().for_each(|(i, item)| { |
| if let Some(a) = item.into().native { |
| unsafe { |
| // SAFETY: There will be enough space in the buffers due to the trusted len size |
| // hint |
| bit_util::set_bit_raw(null_slice.as_mut_ptr(), i); |
| if a { |
| bit_util::set_bit_raw(data.as_mut_ptr(), i); |
| } |
| } |
| } |
| }); |
| |
| let values = BooleanBuffer::new(val_builder.into(), 0, data_len); |
| let nulls = NullBuffer::from_unsliced_buffer(null_builder, data_len); |
| BooleanArray::new(values, nulls) |
| } |
| } |
| |
| impl From<BooleanBuffer> for BooleanArray { |
| fn from(values: BooleanBuffer) -> Self { |
| Self { |
| values, |
| nulls: None, |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| // Captures the values-buffer identity for a BooleanArray so tests can assert |
| // whether an operation reused the original allocation or produced a new one. |
| struct PointerInfo { |
| ptr: *const u8, |
| offset: usize, |
| len: usize, |
| } |
| |
| impl PointerInfo { |
| // Record the current values buffer pointer plus bit offset/length. The |
| // offset/length checks ensure a logically equivalent slice wasn't rebuilt |
| // with a different view over the same allocation. |
| fn new(array: &BooleanArray) -> Self { |
| Self { |
| ptr: array.values().inner().as_ptr(), |
| offset: array.values().offset(), |
| len: array.values().len(), |
| } |
| } |
| |
| // Assert that the array still points at the exact same values buffer and |
| // preserves the same bit view. |
| fn assert_same(&self, array: &BooleanArray) { |
| assert_eq!(array.values().inner().as_ptr(), self.ptr); |
| assert_eq!(array.values().offset(), self.offset); |
| assert_eq!(array.values().len(), self.len); |
| } |
| |
| // Assert that the array now points at a different values allocation, |
| // indicating the operation fell back to an allocating path. |
| fn assert_different(&self, array: &BooleanArray) { |
| assert_ne!(array.values().inner().as_ptr(), self.ptr); |
| } |
| } |
| use arrow_buffer::Buffer; |
| use rand::{Rng, rng}; |
| |
| #[test] |
| fn test_boolean_fmt_debug() { |
| let arr = BooleanArray::from(vec![true, false, false]); |
| assert_eq!( |
| "BooleanArray\n[\n true,\n false,\n false,\n]", |
| format!("{arr:?}") |
| ); |
| } |
| |
| #[test] |
| fn test_boolean_with_null_fmt_debug() { |
| let mut builder = BooleanArray::builder(3); |
| builder.append_value(true); |
| builder.append_null(); |
| builder.append_value(false); |
| let arr = builder.finish(); |
| assert_eq!( |
| "BooleanArray\n[\n true,\n null,\n false,\n]", |
| format!("{arr:?}") |
| ); |
| } |
| |
| #[test] |
| fn test_boolean_array_from_vec() { |
| let buf = Buffer::from([10_u8]); |
| let arr = BooleanArray::from(vec![false, true, false, true]); |
| assert_eq!(&buf, arr.values().inner()); |
| assert_eq!(4, arr.len()); |
| assert_eq!(0, arr.offset()); |
| assert_eq!(0, arr.null_count()); |
| for i in 0..4 { |
| assert!(!arr.is_null(i)); |
| assert!(arr.is_valid(i)); |
| assert_eq!(i == 1 || i == 3, arr.value(i), "failed at {i}") |
| } |
| } |
| |
| #[test] |
| fn test_boolean_array_from_vec_option() { |
| let buf = Buffer::from([10_u8]); |
| let arr = BooleanArray::from(vec![Some(false), Some(true), None, Some(true)]); |
| assert_eq!(&buf, arr.values().inner()); |
| assert_eq!(4, arr.len()); |
| assert_eq!(0, arr.offset()); |
| assert_eq!(1, arr.null_count()); |
| for i in 0..4 { |
| if i == 2 { |
| assert!(arr.is_null(i)); |
| assert!(!arr.is_valid(i)); |
| } else { |
| assert!(!arr.is_null(i)); |
| assert!(arr.is_valid(i)); |
| assert_eq!(i == 1 || i == 3, arr.value(i), "failed at {i}") |
| } |
| } |
| } |
| |
| #[test] |
| fn test_boolean_array_from_packed() { |
| let v = [1_u8, 2_u8, 3_u8]; |
| let arr = BooleanArray::new_from_packed(v, 0, 24); |
| assert_eq!(24, arr.len()); |
| assert_eq!(0, arr.offset()); |
| assert_eq!(0, arr.null_count()); |
| assert!(arr.nulls.is_none()); |
| for i in 0..24 { |
| assert!(!arr.is_null(i)); |
| assert!(arr.is_valid(i)); |
| assert_eq!( |
| i == 0 || i == 9 || i == 16 || i == 17, |
| arr.value(i), |
| "failed t {i}" |
| ) |
| } |
| } |
| |
| #[test] |
| fn test_boolean_array_from_slice_u8() { |
| let v: Vec<u8> = vec![1, 2, 3]; |
| let slice = &v[..]; |
| let arr = BooleanArray::new_from_u8(slice); |
| assert_eq!(24, arr.len()); |
| assert_eq!(0, arr.offset()); |
| assert_eq!(0, arr.null_count()); |
| assert!(arr.nulls().is_none()); |
| for i in 0..24 { |
| assert!(!arr.is_null(i)); |
| assert!(arr.is_valid(i)); |
| assert_eq!( |
| i == 0 || i == 9 || i == 16 || i == 17, |
| arr.value(i), |
| "failed t {i}" |
| ) |
| } |
| } |
| |
| #[test] |
| fn test_boolean_array_from_iter() { |
| let v = vec![Some(false), Some(true), Some(false), Some(true)]; |
| let arr = v.into_iter().collect::<BooleanArray>(); |
| assert_eq!(4, arr.len()); |
| assert_eq!(0, arr.offset()); |
| assert_eq!(0, arr.null_count()); |
| assert!(arr.nulls().is_none()); |
| for i in 0..3 { |
| assert!(!arr.is_null(i)); |
| assert!(arr.is_valid(i)); |
| assert_eq!(i == 1 || i == 3, arr.value(i), "failed at {i}") |
| } |
| } |
| |
| #[test] |
| fn test_boolean_array_from_non_nullable_iter() { |
| let v = vec![true, false, true]; |
| let arr = v.into_iter().collect::<BooleanArray>(); |
| assert_eq!(3, arr.len()); |
| assert_eq!(0, arr.offset()); |
| assert_eq!(0, arr.null_count()); |
| assert!(arr.nulls().is_none()); |
| |
| assert!(arr.value(0)); |
| assert!(!arr.value(1)); |
| assert!(arr.value(2)); |
| } |
| |
| #[test] |
| fn test_boolean_array_from_nullable_iter() { |
| let v = vec![Some(true), None, Some(false), None]; |
| let arr = v.into_iter().collect::<BooleanArray>(); |
| assert_eq!(4, arr.len()); |
| assert_eq!(0, arr.offset()); |
| assert_eq!(2, arr.null_count()); |
| assert!(arr.nulls().is_some()); |
| |
| assert!(arr.is_valid(0)); |
| assert!(arr.is_null(1)); |
| assert!(arr.is_valid(2)); |
| assert!(arr.is_null(3)); |
| |
| assert!(arr.value(0)); |
| assert!(!arr.value(2)); |
| } |
| |
| #[test] |
| fn test_boolean_array_from_nullable_trusted_len_iter() { |
| // Should exhibit the same behavior as `from_iter`, which is tested above. |
| let v = vec![Some(true), None, Some(false), None]; |
| let expected = v.clone().into_iter().collect::<BooleanArray>(); |
| let actual = unsafe { |
| // SAFETY: `v` has trusted length |
| BooleanArray::from_trusted_len_iter(v.into_iter()) |
| }; |
| assert_eq!(expected, actual); |
| } |
| |
| #[test] |
| fn test_boolean_array_from_iter_with_larger_upper_bound() { |
| // See https://github.com/apache/arrow-rs/issues/8505 |
| // This returns an upper size hint of 4 |
| let iterator = vec![Some(true), None, Some(false), None] |
| .into_iter() |
| .filter(Option::is_some); |
| let arr = iterator.collect::<BooleanArray>(); |
| assert_eq!(2, arr.len()); |
| } |
| |
| #[test] |
| fn test_boolean_array_builder() { |
| // Test building a boolean array with ArrayData builder and offset |
| // 000011011 |
| let buf = Buffer::from([27_u8]); |
| let buf2 = buf.clone(); |
| let data = ArrayData::builder(DataType::Boolean) |
| .len(5) |
| .offset(2) |
| .add_buffer(buf) |
| .build() |
| .unwrap(); |
| let arr = BooleanArray::from(data); |
| assert_eq!(&buf2, arr.values().inner()); |
| assert_eq!(5, arr.len()); |
| assert_eq!(2, arr.offset()); |
| assert_eq!(0, arr.null_count()); |
| for i in 0..3 { |
| assert_eq!(i != 0, arr.value(i), "failed at {i}"); |
| } |
| } |
| |
| #[test] |
| #[should_panic( |
| expected = "Trying to access an element at index 4 from a BooleanArray of length 3" |
| )] |
| fn test_fixed_size_binary_array_get_value_index_out_of_bound() { |
| let v = vec![Some(true), None, Some(false)]; |
| let array = v.into_iter().collect::<BooleanArray>(); |
| |
| array.value(4); |
| } |
| |
| #[test] |
| #[should_panic(expected = "BooleanArray data should contain a single buffer only \ |
| (values buffer)")] |
| // Different error messages, so skip for now |
| // https://github.com/apache/arrow-rs/issues/1545 |
| #[cfg(not(feature = "force_validate"))] |
| fn test_boolean_array_invalid_buffer_len() { |
| let data = unsafe { |
| ArrayData::builder(DataType::Boolean) |
| .len(5) |
| .build_unchecked() |
| }; |
| drop(BooleanArray::from(data)); |
| } |
| |
| #[test] |
| #[should_panic(expected = "BooleanArray expected ArrayData with type Boolean got Int32")] |
| fn test_from_array_data_validation() { |
| let _ = BooleanArray::from(ArrayData::new_empty(&DataType::Int32)); |
| } |
| |
| #[test] |
| #[cfg_attr(miri, ignore)] // Takes too long |
| fn test_true_false_count() { |
| let mut rng = rng(); |
| |
| for _ in 0..10 { |
| // No nulls |
| let d: Vec<_> = (0..2000).map(|_| rng.random_bool(0.5)).collect(); |
| let b = BooleanArray::from(d.clone()); |
| |
| let expected_true = d.iter().filter(|x| **x).count(); |
| assert_eq!(b.true_count(), expected_true); |
| assert_eq!(b.false_count(), d.len() - expected_true); |
| |
| // With nulls |
| let d: Vec<_> = (0..2000) |
| .map(|_| rng.random_bool(0.5).then(|| rng.random_bool(0.5))) |
| .collect(); |
| let b = BooleanArray::from(d.clone()); |
| |
| let expected_true = d.iter().filter(|x| matches!(x, Some(true))).count(); |
| assert_eq!(b.true_count(), expected_true); |
| |
| let expected_false = d.iter().filter(|x| matches!(x, Some(false))).count(); |
| assert_eq!(b.false_count(), expected_false); |
| } |
| } |
| |
| #[test] |
| fn test_into_parts() { |
| let boolean_array = [Some(true), None, Some(false)] |
| .into_iter() |
| .collect::<BooleanArray>(); |
| let (values, nulls) = boolean_array.into_parts(); |
| assert_eq!(values.values(), &[0b0000_0001]); |
| assert!(nulls.is_some()); |
| assert_eq!(nulls.unwrap().buffer().as_slice(), &[0b0000_0101]); |
| |
| let boolean_array = |
| BooleanArray::from(vec![false, false, false, false, false, false, false, true]); |
| let (values, nulls) = boolean_array.into_parts(); |
| assert_eq!(values.values(), &[0b1000_0000]); |
| assert!(nulls.is_none()); |
| } |
| |
| #[test] |
| fn test_new_null_array() { |
| let arr = BooleanArray::new_null(5); |
| |
| assert_eq!(arr.len(), 5); |
| assert_eq!(arr.null_count(), 5); |
| assert_eq!(arr.true_count(), 0); |
| assert_eq!(arr.false_count(), 0); |
| |
| for i in 0..5 { |
| assert!(arr.is_null(i)); |
| assert!(!arr.is_valid(i)); |
| } |
| } |
| |
| #[test] |
| fn test_slice_with_nulls() { |
| let arr = BooleanArray::from(vec![Some(true), None, Some(false)]); |
| let sliced = arr.slice(1, 2); |
| |
| assert_eq!(sliced.len(), 2); |
| assert_eq!(sliced.null_count(), 1); |
| |
| assert!(sliced.is_null(0)); |
| assert!(sliced.is_valid(1)); |
| assert!(!sliced.value(1)); |
| } |
| |
| #[test] |
| fn test_has_true_has_false_all_true() { |
| let arr = BooleanArray::from(vec![true, true, true]); |
| assert!(arr.has_true()); |
| assert!(!arr.has_false()); |
| } |
| |
| #[test] |
| fn test_has_true_has_false_all_false() { |
| let arr = BooleanArray::from(vec![false, false, false]); |
| assert!(!arr.has_true()); |
| assert!(arr.has_false()); |
| } |
| |
| #[test] |
| fn test_has_true_has_false_mixed() { |
| let arr = BooleanArray::from(vec![true, false, true]); |
| assert!(arr.has_true()); |
| assert!(arr.has_false()); |
| } |
| |
| #[test] |
| fn test_has_true_has_false_empty() { |
| let arr = BooleanArray::from(Vec::<bool>::new()); |
| assert!(!arr.has_true()); |
| assert!(!arr.has_false()); |
| } |
| |
| #[test] |
| fn test_has_true_has_false_nulls_all_valid_true() { |
| let arr = BooleanArray::from(vec![Some(true), None, Some(true)]); |
| assert!(arr.has_true()); |
| assert!(!arr.has_false()); |
| } |
| |
| #[test] |
| fn test_has_true_has_false_nulls_all_valid_false() { |
| let arr = BooleanArray::from(vec![Some(false), None, Some(false)]); |
| assert!(!arr.has_true()); |
| assert!(arr.has_false()); |
| } |
| |
| #[test] |
| fn test_has_true_has_false_all_null() { |
| let arr = BooleanArray::new_null(5); |
| assert!(!arr.has_true()); |
| assert!(!arr.has_false()); |
| } |
| |
| #[test] |
| fn test_has_false_aligned_suffix_all_true() { |
| let arr = BooleanArray::from(vec![true; 129]); |
| assert!(arr.has_true()); |
| assert!(!arr.has_false()); |
| } |
| |
| #[test] |
| fn test_has_false_non_aligned_all_true() { |
| // 65 elements: exercises the remainder path in has_false |
| let arr = BooleanArray::from(vec![true; 65]); |
| assert!(arr.has_true()); |
| assert!(!arr.has_false()); |
| } |
| |
| #[test] |
| fn test_has_false_non_aligned_last_false() { |
| // 64 trues + 1 false: remainder path should find the false |
| let mut values = vec![true; 64]; |
| values.push(false); |
| let arr = BooleanArray::from(values); |
| assert!(arr.has_true()); |
| assert!(arr.has_false()); |
| } |
| |
| #[test] |
| fn test_has_false_exact_64_all_true() { |
| // Exactly 64 elements, no remainder |
| let arr = BooleanArray::from(vec![true; 64]); |
| assert!(arr.has_true()); |
| assert!(!arr.has_false()); |
| } |
| |
| #[test] |
| fn test_has_true_has_false_unaligned_slices() { |
| let cases = [ |
| (1, 129, true, false), |
| (3, 130, true, false), |
| (5, 65, true, false), |
| (7, 64, true, false), |
| ]; |
| |
| let base = BooleanArray::from(vec![true; 300]); |
| |
| for (offset, len, expected_has_true, expected_has_false) in cases { |
| let arr = base.slice(offset, len); |
| assert_eq!( |
| arr.has_true(), |
| expected_has_true, |
| "offset={offset} len={len}" |
| ); |
| assert_eq!( |
| arr.has_false(), |
| expected_has_false, |
| "offset={offset} len={len}" |
| ); |
| } |
| } |
| |
| #[test] |
| fn test_has_true_has_false_exact_multiples_of_64() { |
| let cases = [ |
| (64, true, false), |
| (128, true, false), |
| (192, true, false), |
| (256, true, false), |
| ]; |
| |
| for (len, expected_has_true, expected_has_false) in cases { |
| let arr = BooleanArray::from(vec![true; len]); |
| assert_eq!(arr.has_true(), expected_has_true, "len={len}"); |
| assert_eq!(arr.has_false(), expected_has_false, "len={len}"); |
| } |
| } |
| |
| #[test] |
| fn test_bitwise_unary_not() { |
| let arr = BooleanArray::from(vec![true, false, true, false]); |
| let result = arr.bitwise_unary(|x| !x); |
| let expected = BooleanArray::from(vec![false, true, false, true]); |
| assert_eq!(result, expected); |
| } |
| |
| #[test] |
| fn test_bitwise_unary_preserves_nulls() { |
| let arr = BooleanArray::from(vec![Some(true), None, Some(false), Some(true)]); |
| let result = arr.bitwise_unary(|x| !x); |
| |
| assert_eq!(result.null_count(), 1); |
| assert!(result.is_null(1)); |
| assert!(!result.value(0)); |
| assert!(result.value(2)); |
| assert!(!result.value(3)); |
| } |
| |
| #[test] |
| fn test_bitwise_unary_mut_unshared() { |
| let arr = BooleanArray::from(vec![true, false, true, false]); |
| let info = PointerInfo::new(&arr); |
| let result = arr.bitwise_unary_mut(|x| !x).unwrap(); |
| let expected = BooleanArray::from(vec![false, true, false, true]); |
| assert_eq!(result, expected); |
| info.assert_same(&result); |
| } |
| |
| #[test] |
| fn test_bitwise_unary_mut_shared() { |
| let arr = BooleanArray::from(vec![true, false, true, false]); |
| let info = PointerInfo::new(&arr); |
| let _shared = arr.clone(); |
| let result = arr.bitwise_unary_mut(|x| !x); |
| assert!(result.is_err()); |
| |
| let returned = result.unwrap_err(); |
| assert_eq!(returned, BooleanArray::from(vec![true, false, true, false])); |
| info.assert_same(&returned); |
| } |
| |
| #[test] |
| fn test_bitwise_unary_mut_with_nulls() { |
| let arr = BooleanArray::from(vec![Some(true), None, Some(false)]); |
| let result = arr.bitwise_unary_mut(|x| !x).unwrap(); |
| |
| assert_eq!(result.null_count(), 1); |
| assert!(result.is_null(1)); |
| assert!(!result.value(0)); |
| assert!(result.value(2)); |
| } |
| |
| #[test] |
| fn test_bitwise_unary_mut_or_clone_shared() { |
| let arr = BooleanArray::from(vec![true, false, true]); |
| let info = PointerInfo::new(&arr); |
| let _shared = arr.clone(); |
| let result = arr.bitwise_unary_mut_or_clone(|x| !x); |
| assert_eq!(result, BooleanArray::from(vec![false, true, false])); |
| info.assert_different(&result); |
| } |
| |
| #[test] |
| fn test_bitwise_unary_mut_or_clone_unshared() { |
| // Covers the uniquely-owned fast path in bitwise_unary_mut_or_clone. |
| let arr = BooleanArray::from(vec![true, false, true]); |
| let info = PointerInfo::new(&arr); |
| let result = arr.bitwise_unary_mut_or_clone(|x| !x); |
| assert_eq!(result, BooleanArray::from(vec![false, true, false])); |
| info.assert_same(&result); |
| } |
| |
| #[test] |
| fn test_bitwise_bin_op_and() { |
| let a = BooleanArray::from(vec![true, false, true, true]); |
| let b = BooleanArray::from(vec![true, true, false, true]); |
| let result = a.bitwise_bin_op(&b, |a, b| a & b); |
| assert_eq!(result, BooleanArray::from(vec![true, false, false, true])); |
| } |
| |
| #[test] |
| fn test_bitwise_bin_op_or() { |
| let a = BooleanArray::from(vec![true, false, true, false]); |
| let b = BooleanArray::from(vec![false, true, false, false]); |
| let result = a.bitwise_bin_op(&b, |a, b| a | b); |
| assert_eq!(result, BooleanArray::from(vec![true, true, true, false])); |
| } |
| |
| #[test] |
| fn test_bitwise_bin_op_null_union() { |
| let a = BooleanArray::from(vec![Some(true), None, Some(true), Some(false)]); |
| let b = BooleanArray::from(vec![Some(true), Some(true), None, Some(true)]); |
| let result = a.bitwise_bin_op(&b, |a, b| a & b); |
| |
| assert_eq!(result.null_count(), 2); |
| assert!(result.is_null(1)); |
| assert!(result.is_null(2)); |
| assert!(result.value(0)); |
| assert!(!result.value(3)); |
| } |
| |
| #[test] |
| fn test_bitwise_bin_op_one_nullable() { |
| let a = BooleanArray::from(vec![Some(true), None, Some(true)]); |
| let b = BooleanArray::from(vec![false, true, true]); |
| let result = a.bitwise_bin_op(&b, |a, b| a & b); |
| |
| assert_eq!(result.null_count(), 1); |
| assert!(result.is_null(1)); |
| assert!(!result.value(0)); |
| assert!(result.value(2)); |
| } |
| |
| #[test] |
| fn test_bitwise_bin_op_no_nulls() { |
| let a = BooleanArray::from(vec![true, false, true]); |
| let b = BooleanArray::from(vec![false, true, true]); |
| let result = a.bitwise_bin_op(&b, |a, b| a | b); |
| |
| assert!(result.nulls().is_none()); |
| assert_eq!(result, BooleanArray::from(vec![true, true, true])); |
| } |
| |
| #[test] |
| fn test_bitwise_bin_op_mut_unshared() { |
| let a = BooleanArray::from(vec![true, false, true, true]); |
| let info = PointerInfo::new(&a); |
| let b = BooleanArray::from(vec![true, true, false, true]); |
| let result = a.bitwise_bin_op_mut(&b, |a, b| a & b).unwrap(); |
| assert_eq!(result, BooleanArray::from(vec![true, false, false, true])); |
| info.assert_same(&result); |
| } |
| |
| #[test] |
| fn test_bitwise_bin_op_mut_shared() { |
| let a = BooleanArray::from(vec![true, false, true, true]); |
| let info = PointerInfo::new(&a); |
| let _shared = a.clone(); |
| let result = a.bitwise_bin_op_mut( |
| &BooleanArray::from(vec![true, true, false, true]), |
| |a, b| a & b, |
| ); |
| assert!(result.is_err()); |
| let returned = result.unwrap_err(); |
| info.assert_same(&returned); |
| } |
| |
| #[test] |
| fn test_bitwise_bin_op_mut_with_nulls() { |
| let a = BooleanArray::from(vec![Some(true), None, Some(true), Some(false)]); |
| let b = BooleanArray::from(vec![Some(true), Some(true), None, Some(true)]); |
| let result = a.bitwise_bin_op_mut(&b, |a, b| a & b).unwrap(); |
| |
| assert_eq!(result.null_count(), 2); |
| assert!(result.is_null(1)); |
| assert!(result.is_null(2)); |
| assert!(result.value(0)); |
| assert!(!result.value(3)); |
| } |
| |
| #[test] |
| fn test_bitwise_bin_op_mut_or_clone_shared() { |
| let a = BooleanArray::from(vec![true, false, true, true]); |
| let info = PointerInfo::new(&a); |
| let _shared = a.clone(); |
| let b = BooleanArray::from(vec![true, true, false, true]); |
| let result = a.bitwise_bin_op_mut_or_clone(&b, |a, b| a & b); |
| assert_eq!(result, BooleanArray::from(vec![true, false, false, true])); |
| info.assert_different(&result); |
| } |
| |
| #[test] |
| fn test_bitwise_bin_op_mut_or_clone_shared_with_nulls() { |
| // When the buffer is shared, _mut_or_clone falls back to bitwise_bin_op. |
| // The null union must only be applied once, not double-applied. |
| let a = BooleanArray::from(vec![Some(true), None, Some(true), Some(false)]); |
| let info = PointerInfo::new(&a); |
| let _shared = a.clone(); |
| let b = BooleanArray::from(vec![Some(true), Some(true), None, Some(true)]); |
| |
| let expected = a.bitwise_bin_op(&b, |a, b| a & b); |
| let result = a.bitwise_bin_op_mut_or_clone(&b, |a, b| a & b); |
| |
| assert_eq!(result, expected); |
| assert_eq!(result.null_count(), 2); |
| assert!(result.is_null(1)); |
| assert!(result.is_null(2)); |
| info.assert_different(&result); |
| } |
| |
| #[test] |
| fn test_bitwise_bin_op_mut_or_clone_unshared_with_nulls() { |
| // Covers the uniquely-owned fast path in bitwise_bin_op_mut_or_clone, |
| // including null union on the in-place path. |
| let a = BooleanArray::from(vec![Some(true), None, Some(true), Some(false)]); |
| let info = PointerInfo::new(&a); |
| let b = BooleanArray::from(vec![Some(true), Some(true), None, Some(true)]); |
| let result = a.bitwise_bin_op_mut_or_clone(&b, |a, b| a & b); |
| |
| assert_eq!(result.null_count(), 2); |
| assert!(result.is_null(1)); |
| assert!(result.is_null(2)); |
| assert!(result.value(0)); |
| assert!(!result.value(3)); |
| info.assert_same(&result); |
| } |
| |
| #[test] |
| fn test_bitwise_unary_empty() { |
| let arr = BooleanArray::from(Vec::<bool>::new()); |
| let result = arr.bitwise_unary(|x| !x); |
| assert_eq!(result.len(), 0); |
| } |
| |
| #[test] |
| fn test_bitwise_bin_op_empty() { |
| let a = BooleanArray::from(Vec::<bool>::new()); |
| let b = BooleanArray::from(Vec::<bool>::new()); |
| let result = a.bitwise_bin_op(&b, |a, b| a & b); |
| assert_eq!(result.len(), 0); |
| } |
| |
| #[test] |
| fn test_bitwise_unary_sliced() { |
| // Slicing creates a non-zero offset into the underlying buffer. |
| let arr = BooleanArray::from(vec![true, false, true, true, false]); |
| let sliced = arr.slice(1, 3); // [false, true, true] |
| |
| let result = sliced.bitwise_unary(|x| !x); |
| assert_eq!(result.len(), 3); |
| assert!(result.value(0)); |
| assert!(!result.value(1)); |
| assert!(!result.value(2)); |
| } |
| |
| #[test] |
| fn test_bitwise_unary_mut_sliced() { |
| // Slicing shares the buffer, so _mut must return Err. |
| let arr = BooleanArray::from(vec![true, false, true, true, false]); |
| let sliced = arr.slice(1, 3); |
| assert!(sliced.bitwise_unary_mut(|x| !x).is_err()); |
| } |
| |
| #[test] |
| fn test_bitwise_unary_mut_or_clone_sliced() { |
| // Slicing shares the buffer, so _mut_or_clone falls back to allocating. |
| let arr = BooleanArray::from(vec![true, false, true, true, false]); |
| let sliced = arr.slice(1, 3); // [false, true, true] |
| |
| let result = sliced.bitwise_unary_mut_or_clone(|x| !x); |
| assert_eq!(result.len(), 3); |
| assert!(result.value(0)); |
| assert!(!result.value(1)); |
| assert!(!result.value(2)); |
| } |
| |
| #[test] |
| fn test_bitwise_bin_op_different_offsets() { |
| // Left and right sliced to different offsets exercises misaligned |
| // bit handling in from_bitwise_binary_op. |
| let left_full = BooleanArray::from(vec![false, true, false, true, true]); |
| let right_full = BooleanArray::from(vec![true, true, true, false, true, false]); |
| |
| let left = left_full.slice(1, 3); // [true, false, true] |
| let right = right_full.slice(2, 3); // [true, false, true] |
| |
| let result = left.bitwise_bin_op(&right, |a, b| a & b); |
| assert_eq!(result.len(), 3); |
| assert!(result.value(0)); |
| assert!(!result.value(1)); |
| assert!(result.value(2)); |
| } |
| |
| #[test] |
| fn test_bitwise_bin_op_mut_or_clone_different_offsets() { |
| // Both sliced (shared buffers), so falls back to allocating path. |
| let left_full = BooleanArray::from(vec![false, true, true, false, true]); |
| let right_full = BooleanArray::from(vec![true, true, false, false, true, false]); |
| |
| let left = left_full.slice(1, 3); // [true, true, false] |
| let right = right_full.slice(2, 3); // [false, false, true] |
| |
| let expected = left.bitwise_bin_op(&right, |a, b| a & b); |
| let result = left.bitwise_bin_op_mut_or_clone(&right, |a, b| a & b); |
| assert_eq!(result, expected); |
| } |
| } |