chronix_engine/segment/
validity.rs1use arrow::buffer::{BooleanBuffer, NullBuffer};
21
22#[must_use]
24pub const fn bitmap_len(len: usize) -> usize {
25 len.div_ceil(8)
26}
27
28#[derive(Debug)]
30pub struct ValidityBuilder {
31 bits: Vec<u8>,
32 len: usize,
33 nulls: usize,
34}
35
36impl ValidityBuilder {
37 #[must_use]
39 pub fn with_capacity(capacity: usize) -> Self {
40 Self {
41 bits: vec![0u8; bitmap_len(capacity)],
42 len: 0,
43 nulls: 0,
44 }
45 }
46
47 pub fn push(&mut self, valid: bool) {
49 let byte = self.len / 8;
50 if byte >= self.bits.len() {
51 self.bits.resize(byte + 1, 0);
52 }
53 if valid {
54 self.bits[byte] |= 1u8 << (self.len % 8);
55 } else {
56 self.nulls += 1;
57 }
58 self.len += 1;
59 }
60
61 #[must_use]
63 pub fn len(&self) -> usize {
64 self.len
65 }
66
67 #[must_use]
69 pub fn is_empty(&self) -> bool {
70 self.len == 0
71 }
72
73 #[must_use]
75 pub fn null_count(&self) -> usize {
76 self.nulls
77 }
78
79 #[must_use]
84 pub fn finish(mut self) -> Option<Vec<u8>> {
85 if self.nulls == 0 {
86 return None;
87 }
88 self.bits.truncate(bitmap_len(self.len));
89 Some(self.bits)
90 }
91}
92
93#[must_use]
99pub fn null_buffer_from_bytes(bytes: &[u8], len: usize) -> Option<NullBuffer> {
100 if len == 0 || bytes.len() < bitmap_len(len) {
101 return None;
102 }
103 let buffer = BooleanBuffer::new(bytes[..bitmap_len(len)].to_vec().into(), 0, len);
104 let nulls = NullBuffer::new(buffer);
105 if nulls.null_count() == 0 {
106 None
107 } else {
108 Some(nulls)
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn dense_block_stores_no_bitmap() {
118 let mut b = ValidityBuilder::with_capacity(100);
119 for _ in 0..100 {
120 b.push(true);
121 }
122 assert_eq!(b.null_count(), 0);
123 assert!(b.finish().is_none());
124 }
125
126 #[test]
127 fn roundtrips_sparse_pattern() {
128 let pattern: Vec<bool> = (0..137).map(|i| i % 3 != 0).collect();
129 let mut b = ValidityBuilder::with_capacity(pattern.len());
130 for &v in &pattern {
131 b.push(v);
132 }
133 let expected_nulls = pattern.iter().filter(|v| !**v).count();
134 assert_eq!(b.null_count(), expected_nulls);
135
136 let bytes = b.finish().expect("has nulls");
137 assert_eq!(bytes.len(), bitmap_len(pattern.len()));
138
139 let nulls = null_buffer_from_bytes(&bytes, pattern.len()).expect("has nulls");
140 assert_eq!(nulls.null_count(), expected_nulls);
141 for (i, &want) in pattern.iter().enumerate() {
142 assert_eq!(nulls.is_valid(i), want, "row {i}");
143 }
144 }
145
146 #[test]
147 fn all_null_block() {
148 let mut b = ValidityBuilder::with_capacity(9);
149 for _ in 0..9 {
150 b.push(false);
151 }
152 let bytes = b.finish().expect("has nulls");
153 let nulls = null_buffer_from_bytes(&bytes, 9).expect("has nulls");
154 assert_eq!(nulls.null_count(), 9);
155 }
156
157 #[test]
158 fn truncated_bitmap_is_rejected() {
159 assert!(null_buffer_from_bytes(&[0xFF], 100).is_none());
160 assert!(null_buffer_from_bytes(&[], 0).is_none());
161 }
162
163 #[test]
164 fn all_valid_bitmap_yields_no_null_buffer() {
165 assert!(null_buffer_from_bytes(&[0xFF], 8).is_none());
167 }
168
169 #[test]
170 fn bitmap_len_rounds_up() {
171 assert_eq!(bitmap_len(0), 0);
172 assert_eq!(bitmap_len(1), 1);
173 assert_eq!(bitmap_len(8), 1);
174 assert_eq!(bitmap_len(9), 2);
175 }
176}