Skip to main content

reifydb_core/value/column/
mask.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4// Per-row selection bitmap. Set bit = row is selected/kept. This is the dual
5// of `NoneBitmap` - the storage layout is identical but the semantics are
6// opposite, so the two are kept as distinct types to prevent accidental mixing.
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct RowMask {
9	words: Vec<u64>,
10	len: usize,
11}
12
13impl RowMask {
14	pub fn all_set(len: usize) -> Self {
15		let word_count = len.div_ceil(64);
16		let mut words = vec![u64::MAX; word_count];
17		let trailing = len % 64;
18		if trailing != 0 && word_count > 0 {
19			words[word_count - 1] = (1u64 << trailing) - 1;
20		}
21		Self {
22			words,
23			len,
24		}
25	}
26
27	pub fn none_set(len: usize) -> Self {
28		Self {
29			words: vec![0u64; len.div_ceil(64)],
30			len,
31		}
32	}
33
34	pub fn len(&self) -> usize {
35		self.len
36	}
37
38	pub fn is_empty(&self) -> bool {
39		self.len == 0
40	}
41
42	pub fn get(&self, row: usize) -> bool {
43		debug_assert!(row < self.len);
44		(self.words[row / 64] >> (row % 64)) & 1 == 1
45	}
46
47	pub fn set(&mut self, row: usize, value: bool) {
48		debug_assert!(row < self.len);
49		let word = &mut self.words[row / 64];
50		let bit = 1u64 << (row % 64);
51		if value {
52			*word |= bit;
53		} else {
54			*word &= !bit;
55		}
56	}
57
58	pub fn popcount(&self) -> usize {
59		let word_count = self.words.len();
60		if word_count == 0 {
61			return 0;
62		}
63		let mut count = 0usize;
64		for &w in &self.words[..word_count - 1] {
65			count += w.count_ones() as usize;
66		}
67		let trailing = self.len - 64 * (word_count - 1);
68		let mask = if trailing == 64 {
69			u64::MAX
70		} else {
71			(1u64 << trailing) - 1
72		};
73		count += (self.words[word_count - 1] & mask).count_ones() as usize;
74		count
75	}
76
77	pub fn and(&self, other: &Self) -> Self {
78		assert_eq!(self.len, other.len, "RowMask::and length mismatch");
79		let words = self.words.iter().zip(&other.words).map(|(a, b)| a & b).collect();
80		Self {
81			words,
82			len: self.len,
83		}
84	}
85
86	pub fn or(&self, other: &Self) -> Self {
87		assert_eq!(self.len, other.len, "RowMask::or length mismatch");
88		let words = self.words.iter().zip(&other.words).map(|(a, b)| a | b).collect();
89		Self {
90			words,
91			len: self.len,
92		}
93	}
94
95	pub fn not(&self) -> Self {
96		let word_count = self.words.len();
97		let mut words: Vec<u64> = self.words.iter().map(|w| !w).collect();
98		let trailing = self.len % 64;
99		if trailing != 0 && word_count > 0 {
100			words[word_count - 1] &= (1u64 << trailing) - 1;
101		}
102		Self {
103			words,
104			len: self.len,
105		}
106	}
107
108	pub fn slice(&self, start: usize, end: usize) -> Self {
109		debug_assert!(start <= end, "RowMask::slice: start {start} > end {end}");
110		debug_assert!(end <= self.len, "RowMask::slice: end {end} > len {}", self.len);
111		let new_len = end - start;
112		let mut out = Self::none_set(new_len);
113		for i in 0..new_len {
114			if self.get(start + i) {
115				out.set(i, true);
116			}
117		}
118		out
119	}
120
121	pub fn concat(parts: &[Self]) -> Self {
122		let total: usize = parts.iter().map(|m| m.len).sum();
123		let mut out = Self::none_set(total);
124		let mut row_offset = 0;
125		for part in parts {
126			for i in 0..part.len {
127				if part.get(i) {
128					out.set(row_offset + i, true);
129				}
130			}
131			row_offset += part.len;
132		}
133		out
134	}
135}
136
137#[cfg(test)]
138mod tests {
139	use super::*;
140
141	#[test]
142	fn all_set_counts_every_row() {
143		let m = RowMask::all_set(100);
144		assert_eq!(m.popcount(), 100);
145		assert!(m.get(0));
146		assert!(m.get(99));
147	}
148
149	#[test]
150	fn none_set_has_zero_popcount() {
151		let m = RowMask::none_set(10);
152		assert_eq!(m.popcount(), 0);
153		assert!(!m.get(0));
154	}
155
156	#[test]
157	fn not_inverts_within_length_only() {
158		let mut m = RowMask::none_set(65);
159		m.set(0, true);
160		m.set(64, true);
161		let inverted = m.not();
162		assert_eq!(inverted.popcount(), 63);
163		assert!(!inverted.get(0));
164		assert!(inverted.get(1));
165		assert!(!inverted.get(64));
166	}
167
168	#[test]
169	fn and_or_combine_masks() {
170		let mut a = RowMask::none_set(8);
171		a.set(1, true);
172		a.set(3, true);
173		a.set(5, true);
174		let mut b = RowMask::none_set(8);
175		b.set(3, true);
176		b.set(5, true);
177		b.set(7, true);
178		assert_eq!(a.and(&b).popcount(), 2);
179		assert_eq!(a.or(&b).popcount(), 4);
180	}
181
182	#[test]
183	fn concat_appends_each_part_at_its_offset() {
184		let mut a = RowMask::none_set(3);
185		a.set(0, true);
186		a.set(2, true);
187		let mut b = RowMask::none_set(2);
188		b.set(1, true);
189		let mut c = RowMask::none_set(4);
190		c.set(0, true);
191		c.set(3, true);
192		let combined = RowMask::concat(&[a, b, c]);
193		assert_eq!(combined.len(), 9);
194		assert!(combined.get(0));
195		assert!(!combined.get(1));
196		assert!(combined.get(2));
197		assert!(!combined.get(3));
198		assert!(combined.get(4));
199		assert!(combined.get(5));
200		assert!(!combined.get(6));
201		assert!(!combined.get(7));
202		assert!(combined.get(8));
203	}
204
205	#[test]
206	fn concat_handles_word_boundary_crossings() {
207		let a = RowMask::all_set(70);
208		let b = RowMask::all_set(70);
209		let combined = RowMask::concat(&[a, b]);
210		assert_eq!(combined.len(), 140);
211		assert_eq!(combined.popcount(), 140);
212	}
213
214	#[test]
215	fn concat_empty_parts_yield_empty_mask() {
216		let combined = RowMask::concat(&[]);
217		assert_eq!(combined.len(), 0);
218		assert_eq!(combined.popcount(), 0);
219	}
220
221	#[test]
222	fn slice_extracts_inner_window() {
223		let mut m = RowMask::none_set(8);
224		m.set(1, true);
225		m.set(3, true);
226		m.set(5, true);
227		m.set(7, true);
228		let s = m.slice(2, 6);
229		assert_eq!(s.len(), 4);
230		assert!(!s.get(0));
231		assert!(s.get(1));
232		assert!(!s.get(2));
233		assert!(s.get(3));
234	}
235
236	#[test]
237	fn slice_crosses_word_boundary() {
238		let mut m = RowMask::none_set(140);
239		m.set(60, true);
240		m.set(64, true);
241		m.set(70, true);
242		let s = m.slice(50, 80);
243		assert_eq!(s.len(), 30);
244		assert_eq!(s.popcount(), 3);
245		assert!(s.get(10));
246		assert!(s.get(14));
247		assert!(s.get(20));
248	}
249
250	#[test]
251	fn slice_full_range_equals_self() {
252		let mut m = RowMask::none_set(10);
253		m.set(0, true);
254		m.set(4, true);
255		m.set(9, true);
256		let s = m.slice(0, 10);
257		assert_eq!(s, m);
258	}
259
260	#[test]
261	fn slice_empty_range_yields_empty_mask() {
262		let m = RowMask::all_set(10);
263		let s = m.slice(5, 5);
264		assert_eq!(s.len(), 0);
265		assert_eq!(s.popcount(), 0);
266	}
267}