1pub const PACKED_MAX: usize = u16::MAX as usize;
32
33#[cfg(not(feature = "std"))]
42use crate::nostd_prelude::*;
43#[cfg(not(feature = "std"))]
44use alloc::vec;
45
46pub type ColumnNames = alloc::sync::Arc<[Vec<u8>]>;
50
51#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct PackedRow(Box<PackedInner>);
65
66#[derive(Debug, Clone, PartialEq, Eq)]
67struct PackedInner {
68 cols: ColumnNames,
69 buf: Box<[u8]>,
70}
71
72impl PackedRow {
73 pub fn build(names: &ColumnNames, cols: &[Option<&[u8]>]) -> Option<Self> {
78 debug_assert_eq!(names.len(), cols.len(), "one value slot per declared column");
79 let ncol = u16::try_from(cols.len()).ok()?;
80 let total: usize = cols.iter().flatten().map(|v| v.len()).sum();
81 if total > PACKED_MAX {
82 return None;
83 }
84 let bitmap = ncol.div_ceil(8) as usize;
85 let header = 2 + bitmap + cols.len() * 2;
86 let mut buf = vec![0u8; header + total];
87 buf[..2].copy_from_slice(&ncol.to_le_bytes());
88 let mut end = 0usize;
89 for (i, c) in cols.iter().enumerate() {
90 if let Some(v) = c {
91 buf[2 + i / 8] |= 1 << (i % 8);
92 buf[header + end..header + end + v.len()].copy_from_slice(v);
93 end += v.len();
94 }
95 let at = 2 + bitmap + i * 2;
96 buf[at..at + 2].copy_from_slice(&(end as u16).to_le_bytes());
97 }
98 Some(PackedRow(Box::new(PackedInner { cols: names.clone(), buf: buf.into_boxed_slice() })))
99 }
100
101 pub fn columns(&self) -> usize {
103 u16::from_le_bytes([self.0.buf[0], self.0.buf[1]]) as usize
104 }
105
106 pub fn has(&self, i: usize) -> bool {
108 i < self.columns() && self.0.buf[2 + i / 8] & (1 << (i % 8)) != 0
109 }
110
111 pub fn get_named(&self, field: &[u8]) -> Option<&[u8]> {
119 let i = self.0.cols.iter().position(|c| c == field)?;
120 self.get(i)
121 }
122
123 pub fn has_named(&self, field: &[u8]) -> bool {
125 self.0.cols.iter().position(|c| c == field).is_some_and(|i| self.has(i))
126 }
127
128 pub fn get(&self, i: usize) -> Option<&[u8]> {
130 if !self.has(i) {
131 return None;
132 }
133 let bitmap = (self.columns() as u16).div_ceil(8) as usize;
134 let header = 2 + bitmap + self.columns() * 2;
135 let end_at = |j: usize| {
136 let at = 2 + bitmap + j * 2;
137 u16::from_le_bytes([self.0.buf[at], self.0.buf[at + 1]]) as usize
138 };
139 let start = if i == 0 { 0 } else { end_at(i - 1) };
140 Some(&self.0.buf[header + start..header + end_at(i)])
141 }
142
143 pub fn set_same_width(&mut self, i: usize, v: &[u8]) -> bool {
156 let Some(old) = self.get(i) else { return false };
157 if old.len() != v.len() {
158 return false;
159 }
160 let bitmap = (self.columns() as u16).div_ceil(8) as usize;
161 let header = 2 + bitmap + self.columns() * 2;
162 let start = if i == 0 {
163 0
164 } else {
165 let at = 2 + bitmap + (i - 1) * 2;
166 u16::from_le_bytes([self.0.buf[at], self.0.buf[at + 1]]) as usize
167 };
168 self.0.buf[header + start..header + start + v.len()].copy_from_slice(v);
169 true
170 }
171
172 pub fn with_column(&self, i: usize, v: Option<&[u8]>) -> Option<Self> {
175 let mut cols: Vec<Option<&[u8]>> = (0..self.columns()).map(|j| self.get(j)).collect();
176 *cols.get_mut(i)? = v;
177 PackedRow::build(&self.0.cols, &cols)
178 }
179
180 pub fn names(&self) -> &ColumnNames {
182 &self.0.cols
183 }
184
185 pub fn fields(&self) -> impl Iterator<Item = (&[u8], &[u8])> {
188 (0..self.columns())
189 .filter_map(move |i| Some((self.0.cols.get(i)?.as_slice(), self.get(i)?)))
190 }
191
192 pub fn heap_bytes(&self) -> usize {
195 self.0.buf.len() + core::mem::size_of::<PackedInner>()
196 }
197
198 pub fn len(&self) -> usize {
200 (0..self.columns()).filter(|&i| self.has(i)).count()
201 }
202
203 pub fn is_empty(&self) -> bool {
205 self.len() == 0
206 }
207}
208
209#[cfg(test)]
210pub(crate) mod tests {
211 use super::*;
212
213 pub(crate) fn names(n: usize) -> ColumnNames {
216 (0..n).map(|i| format!("c{i}").into_bytes()).collect()
217 }
218
219 #[test]
220 fn round_trips_every_column_including_absent_and_empty() {
221 let cols: Vec<Option<&[u8]>> =
222 vec![Some(&b"id7"[..]), None, Some(&b""[..]), Some(&b"a longer value"[..])];
223 let r = PackedRow::build(&names(cols.len()), &cols).expect("fits");
224 assert_eq!(r.columns(), 4);
225 for (i, want) in cols.iter().enumerate() {
226 assert_eq!(r.get(i), *want, "column {i}");
227 }
228 assert!(!r.has(1));
231 assert!(r.has(2));
232 assert_eq!(r.len(), 3);
233 }
234
235 #[test]
236 fn the_cost_scales_with_the_row_rather_than_sitting_on_a_floor() {
237 let three = PackedRow::build(&names(3), &[Some(&b"x"[..]); 3]).expect("fits");
239 let twelve = PackedRow::build(&names(12), &[Some(&b"x"[..]); 12]).expect("fits");
240 assert!(
241 twelve.heap_bytes() > three.heap_bytes(),
242 "a wider row must cost more, not the same: {} vs {}",
243 three.heap_bytes(),
244 twelve.heap_bytes()
245 );
246 let inner = core::mem::size_of::<PackedInner>();
250 assert_eq!(three.heap_bytes(), (2 + 1 + 3 * 2 + 3) + inner);
251 assert_eq!(twelve.heap_bytes(), (2 + 2 + 12 * 2 + 12) + inner);
252 }
253
254 #[test]
255 fn replacing_a_column_leaves_the_others_alone() {
256 let r =
257 PackedRow::build(&names(3), &[Some(&b"a"[..]), Some(&b"bb"[..]), Some(&b"ccc"[..])])
258 .expect("fits");
259 let r2 = r.with_column(1, Some(b"REPLACED")).expect("fits");
260 assert_eq!(r2.get(0), Some(&b"a"[..]));
261 assert_eq!(r2.get(1), Some(&b"REPLACED"[..]));
262 assert_eq!(r2.get(2), Some(&b"ccc"[..]));
263 let r3 = r.with_column(0, None).expect("fits");
264 assert!(!r3.has(0));
265 assert_eq!(r3.get(2), Some(&b"ccc"[..]));
266 }
267
268 #[test]
269 fn an_in_place_write_touches_exactly_one_column() {
270 let n = names(4);
273 let mut r = PackedRow::build(
274 &n,
275 &[Some(&b"aaa"[..]), Some(&b"bbb"[..]), Some(&b"ccc"[..]), Some(&b"ddd"[..])],
276 )
277 .expect("fits");
278 assert!(r.set_same_width(1, b"XXX"), "same width goes in place");
279 assert_eq!(r.get(0), Some(&b"aaa"[..]), "left neighbour untouched");
280 assert_eq!(r.get(1), Some(&b"XXX"[..]));
281 assert_eq!(r.get(2), Some(&b"ccc"[..]), "right neighbour untouched");
282 assert_eq!(r.get(3), Some(&b"ddd"[..]));
283 assert!(r.set_same_width(0, b"ZZZ"));
285 assert_eq!(r.get(0), Some(&b"ZZZ"[..]));
286 assert_eq!(r.get(1), Some(&b"XXX"[..]));
287 assert!(r.set_same_width(3, b"WWW"));
288 assert_eq!(r.get(2), Some(&b"ccc"[..]));
289 assert_eq!(r.get(3), Some(&b"WWW"[..]));
290 }
291
292 #[test]
293 fn an_in_place_write_refuses_anything_that_would_move_an_offset() {
294 let n = names(3);
295 let mut r =
296 PackedRow::build(&n, &[Some(&b"aa"[..]), None, Some(&b"cc"[..])]).expect("fits");
297 assert!(!r.set_same_width(0, b"aaa"), "wider must rebuild");
298 assert!(!r.set_same_width(0, b"a"), "narrower must rebuild");
299 assert!(!r.set_same_width(1, b"xx"), "an absent column must rebuild");
300 assert!(!r.set_same_width(9, b"xx"), "out of range");
301 assert_eq!(r.get(0), Some(&b"aa"[..]));
303 assert_eq!(r.get(2), Some(&b"cc"[..]));
304 assert!(!r.has(1));
305 }
306
307 #[test]
308 fn looks_a_column_up_by_the_name_the_wire_uses() {
309 let n: ColumnNames = vec![b"id".to_vec(), b"name".to_vec(), b"dept".to_vec()].into();
310 let r = PackedRow::build(&n, &[Some(&b"7"[..]), None, Some(&b"eng"[..])]).expect("fits");
311 assert_eq!(r.get_named(b"id"), Some(&b"7"[..]));
312 assert_eq!(r.get_named(b"dept"), Some(&b"eng"[..]));
313 assert_eq!(r.get_named(b"name"), None);
316 assert!(!r.has_named(b"name"));
317 assert_eq!(r.get_named(b"nosuch"), None);
318 assert!(!r.has_named(b"nosuch"));
319 }
320
321 #[test]
322 fn refuses_a_payload_it_cannot_address() {
323 let big = vec![0u8; PACKED_MAX + 1];
324 assert!(PackedRow::build(&names(1), &[Some(&big[..])]).is_none());
325 let just = vec![0u8; PACKED_MAX];
326 assert!(PackedRow::build(&names(1), &[Some(&just[..])]).is_some());
327 }
328}
329
330#[cfg(test)]
331mod cost_tests {
332 use super::tests::names;
333 use super::*;
334
335 #[test]
342 fn a_packed_row_costs_less_than_the_table_it_replaces() {
343 const TABLE_REQUEST: usize = 16 * 48 + 16 + 16; const ARC_AND_MAP: usize = 16 + 56;
345 for (ncol, vlen) in [(3usize, 400usize), (7, 400), (12, 400)] {
346 let v = vec![b'x'; vlen / ncol];
347 let cols: Vec<Option<&[u8]>> = (0..ncol).map(|_| Some(&v[..])).collect();
348 let packed = PackedRow::build(&names(ncol), &cols).expect("fits").heap_bytes();
349 let today = TABLE_REQUEST + ARC_AND_MAP + vlen;
350 assert!(
351 packed * 2 < today,
352 "{ncol} columns: packed {packed} B is not less than half of today's {today} B"
353 );
354 }
355 }
356
357 #[test]
359 fn the_cost_is_not_flat_in_the_column_count() {
360 let v = [b'x'; 32];
361 let w = |n: usize| {
362 PackedRow::build(&names(n), &(0..n).map(|_| Some(&v[..])).collect::<Vec<_>>())
363 .expect("fits")
364 .heap_bytes()
365 };
366 let (a, b) = (w(3), w(12));
367 assert_eq!(b - a, 9 * (32 + 2) + 1, "growth is payload + ends + bitmap byte");
369 }
370}
371
372impl crate::Store {
373 #[doc(hidden)]
380 pub fn is_packed(&mut self, key: &[u8]) -> bool {
381 matches!(self.live_entry(key).map(|e| &e.value), Some(crate::Value::PackedRow(_)))
382 }
383
384 pub fn packed_rows_enabled(&self) -> bool {
386 self.packed_rows
387 }
388
389 pub fn set_packed_rows(&mut self, on: bool) {
395 self.packed_rows = on;
396 }
397
398 fn already_packed(&mut self, key: &[u8]) -> bool {
401 self.is_packed(key)
402 }
403
404 pub fn pack_row(&mut self, key: &[u8], names: &[Vec<u8>]) {
411 if self.already_packed(key) {
412 return;
413 }
414 let Ok(Some(pairs)) = self.hash_pairs(key) else { return };
415 if pairs.iter().any(|(f, _)| !names.iter().any(|n| n == f)) {
416 return;
417 }
418 let cols: Vec<Option<&[u8]>> = names
419 .iter()
420 .map(|n| pairs.iter().find(|(f, _)| f == n).map(|(_, v)| v.as_slice()))
421 .collect();
422 let shared: ColumnNames = names.to_vec().into();
423 let Some(row) = PackedRow::build(&shared, &cols) else { return };
424 if let Some(e) = self.live_entry_mut(key) {
425 e.value = crate::Value::PackedRow(row);
426 }
427 self.reweigh_entry(key);
428 }
429}