From 535f2e46266b3524b48448a50227741c324ece0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Heres?= Date: Tue, 17 Mar 2026 09:13:22 +0100 Subject: [PATCH 1/4] Add mutable operations to BooleanBuffer --- arrow-buffer/src/buffer/boolean.rs | 55 +++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/arrow-buffer/src/buffer/boolean.rs b/arrow-buffer/src/buffer/boolean.rs index c1c7529e0a2d..2cc07eb1f770 100644 --- a/arrow-buffer/src/buffer/boolean.rs +++ b/arrow-buffer/src/buffer/boolean.rs @@ -23,7 +23,7 @@ use crate::{ buffer_bin_xor, }; -use std::ops::{BitAnd, BitOr, BitXor, Not}; +use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not}; /// A slice-able [`Buffer`] containing bit-packed booleans /// @@ -497,6 +497,41 @@ impl BooleanBuffer { self.buffer.claim(pool); } + /// Apply a bitwise binary operation in-place, avoiding allocation if the + /// underlying buffer is not shared. + fn bitwise_bin_op_assign(&mut self, rhs: &BooleanBuffer, op: F) + where + F: FnMut(u64, u64) -> u64, + { + assert_eq!(self.bit_len, rhs.bit_len); + // Try to mutate in place if the buffer is uniquely owned + let buffer = std::mem::take(&mut self.buffer); + match buffer.into_mutable() { + Ok(mut buf) => { + bit_util::apply_bitwise_binary_op( + &mut buf, + self.bit_offset, + &rhs.buffer, + rhs.bit_offset, + self.bit_len, + op, + ); + self.buffer = buf.into(); + } + Err(buf) => { + self.buffer = buf; + *self = BooleanBuffer::from_bitwise_binary_op( + self.values(), + self.bit_offset, + rhs.values(), + rhs.bit_offset, + self.bit_len, + op, + ); + } + } + } + /// Returns an iterator over the bits in this [`BooleanBuffer`] pub fn iter(&self) -> BitIterator<'_> { self.into_iter() @@ -583,6 +618,24 @@ impl BitXor<&BooleanBuffer> for &BooleanBuffer { } } +impl BitAndAssign<&BooleanBuffer> for BooleanBuffer { + fn bitand_assign(&mut self, rhs: &BooleanBuffer) { + self.bitwise_bin_op_assign(rhs, |a, b| a & b); + } +} + +impl BitOrAssign<&BooleanBuffer> for BooleanBuffer { + fn bitor_assign(&mut self, rhs: &BooleanBuffer) { + self.bitwise_bin_op_assign(rhs, |a, b| a | b); + } +} + +impl BitXorAssign<&BooleanBuffer> for BooleanBuffer { + fn bitxor_assign(&mut self, rhs: &BooleanBuffer) { + self.bitwise_bin_op_assign(rhs, |a, b| a ^ b); + } +} + impl<'a> IntoIterator for &'a BooleanBuffer { type Item = bool; type IntoIter = BitIterator<'a>; From e9ae0b35d17dbdc5bd9a5bd58552da0965b6e976 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 17 Mar 2026 17:20:17 -0400 Subject: [PATCH 2/4] Proposed some additional documentation --- arrow-buffer/src/buffer/boolean.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/arrow-buffer/src/buffer/boolean.rs b/arrow-buffer/src/buffer/boolean.rs index 2cc07eb1f770..b7f86b4dc6c4 100644 --- a/arrow-buffer/src/buffer/boolean.rs +++ b/arrow-buffer/src/buffer/boolean.rs @@ -497,8 +497,16 @@ impl BooleanBuffer { self.buffer.claim(pool); } - /// Apply a bitwise binary operation in-place, avoiding allocation if the - /// underlying buffer is not shared. + /// Apply a bitwise binary operation to `self`. + /// + /// If the underlying buffer is uniquely owned, reuses the allocation + /// and updates the bytes in place. If the underlying buffer is shared, + /// returns a newly allocated buffer. + /// + /// # API Notes + /// + /// Note that if the buffer is reused, the result may have non zero offset + /// `0`, but if a new buffer is allocated the offset will always be zero. fn bitwise_bin_op_assign(&mut self, rhs: &BooleanBuffer, op: F) where F: FnMut(u64, u64) -> u64, From 253d767f6d465388eb954c51b2f4bf62499910fd Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 17 Mar 2026 17:30:44 -0400 Subject: [PATCH 3/4] More comments and test --- arrow-buffer/src/buffer/boolean.rs | 38 ++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/arrow-buffer/src/buffer/boolean.rs b/arrow-buffer/src/buffer/boolean.rs index b7f86b4dc6c4..77ac64361dbb 100644 --- a/arrow-buffer/src/buffer/boolean.rs +++ b/arrow-buffer/src/buffer/boolean.rs @@ -67,6 +67,27 @@ use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, N /// Note that the bits marked `?` are not logically part of the mask and may /// contain either `0` or `1` /// +/// # Bitwise Operations +/// +/// `BooleanBuffer` implements the standard bitwise traits for creating a new +/// buffer ([`BitAnd`], [`BitOr`], [`BitXor`], [`Not`]) as well as the assign variants +/// for updating an existing buffer in place when possible ([`BitAndAssign`], +/// [`BitOrAssign`], [`BitXorAssign`]). +/// +/// ``` +/// # use arrow_buffer::BooleanBuffer; +/// let mut left = BooleanBuffer::from(&[true, false, true, true] as &[bool]); +/// let right = BooleanBuffer::from(&[true, true, false, true] as &[bool]); +/// +/// // Create a new buffer by applying bitwise AND +/// let anded = &left & &right; +/// assert_eq!(anded, BooleanBuffer::from(&[true, false, false, true] as &[bool])); +/// +/// // Update `left` in place by applying bitwise AND in place +/// left &= &right; +/// assert_eq!(left, BooleanBuffer::from(&[true, false, false, true] as &[bool])); +/// ``` +/// /// # See Also /// * [`BooleanBufferBuilder`] for building [`BooleanBuffer`] instances /// * [`NullBuffer`] for representing null values in Arrow arrays @@ -790,6 +811,23 @@ mod tests { assert_eq!(boolean_buf1 ^ boolean_buf2, expected); } + #[test] + fn test_boolean_bitand_assign_shared_and_unshared() { + let rhs = BooleanBuffer::from(&[true, true, false, true, false, true][..]); + let original = BooleanBuffer::from(&[true, false, true, true, true, false][..]); + + let mut unshared = BooleanBuffer::from(&[true, false, true, true, true, false][..]); + unshared &= &rhs; + + let mut shared = original.clone(); + let _shared_owner = shared.clone(); + shared &= &rhs; + + let expected = &original & &rhs; + assert_eq!(unshared, expected); + assert_eq!(shared, expected); + } + #[test] fn test_boolean_not() { let offset = 0; From 202d84400399ecff2ebdc7c14185bec990740891 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 17 Mar 2026 18:10:43 -0400 Subject: [PATCH 4/4] cleanup --- arrow-buffer/src/buffer/boolean.rs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/arrow-buffer/src/buffer/boolean.rs b/arrow-buffer/src/buffer/boolean.rs index 77ac64361dbb..bae083b3b208 100644 --- a/arrow-buffer/src/buffer/boolean.rs +++ b/arrow-buffer/src/buffer/boolean.rs @@ -526,8 +526,8 @@ impl BooleanBuffer { /// /// # API Notes /// - /// Note that if the buffer is reused, the result may have non zero offset - /// `0`, but if a new buffer is allocated the offset will always be zero. + /// If the buffer is reused, the result preserves the existing offset, which + /// may be non-zero. fn bitwise_bin_op_assign(&mut self, rhs: &BooleanBuffer, op: F) where F: FnMut(u64, u64) -> u64, @@ -828,6 +828,30 @@ mod tests { assert_eq!(shared, expected); } + #[test] + fn test_boolean_bitor_assign() { + let rhs = BooleanBuffer::from(&[true, true, false, true, false, true][..]); + let original = BooleanBuffer::from(&[true, false, true, true, true, false][..]); + + let mut actual = original.clone(); + actual |= &rhs; + + let expected = &original | &rhs; + assert_eq!(actual, expected); + } + + #[test] + fn test_boolean_bitxor_assign() { + let rhs = BooleanBuffer::from(&[true, true, false, true, false, true][..]); + let original = BooleanBuffer::from(&[true, false, true, true, true, false][..]); + + let mut actual = original.clone(); + actual ^= &rhs; + + let expected = &original ^ &rhs; + assert_eq!(actual, expected); + } + #[test] fn test_boolean_not() { let offset = 0;