1use crate::pack::{
2 push_f64s, push_i64s, push_str, push_u32, push_u32s, read_exact, read_f64s, read_i64s,
3 read_str, read_u32, read_u32s,
4};
5use crate::types::Result as StoreResult;
6use crate::types::{GraphError, Value};
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use std::collections::HashMap;
9
10#[derive(Debug, Default, Clone)]
12struct Bitmap {
13 bits: Vec<u64>,
14}
15
16impl Bitmap {
17 fn contains(&self, i: u32) -> bool {
18 let i = i as usize;
19 let word = i / 64;
20 let bit = i % 64;
21 self.bits.get(word).is_some_and(|w| w & (1u64 << bit) != 0)
22 }
23
24 fn set(&mut self, i: u32) -> bool {
26 let i = i as usize;
27 let word = i / 64;
28 let bit = i % 64;
29 if word >= self.bits.len() {
30 self.bits.resize(word + 1, 0);
31 }
32 let mask = 1u64 << bit;
33 let newly = self.bits[word] & mask == 0;
34 self.bits[word] |= mask;
35 newly
36 }
37
38 fn clear(&mut self, i: u32) -> bool {
40 let i = i as usize;
41 let word = i / 64;
42 let bit = i % 64;
43 let Some(w) = self.bits.get_mut(word) else {
44 return false;
45 };
46 let mask = 1u64 << bit;
47 let was = *w & mask != 0;
48 *w &= !mask;
49 was
50 }
51
52 fn for_each(&self, mut f: impl FnMut(u32)) {
53 for (wi, &word) in self.bits.iter().enumerate() {
54 let mut w = word;
55 while w != 0 {
56 let b = w.trailing_zeros();
57 f(wi as u32 * 64 + b);
58 w &= w - 1;
59 }
60 }
61 }
62
63 fn live_count(&self) -> usize {
64 self.bits.iter().map(|w| w.count_ones() as usize).sum()
65 }
66
67 fn pack(&self, out: &mut Vec<u8>) {
68 push_u32(out, self.bits.len() as u32);
69 for w in &self.bits {
70 out.extend_from_slice(&w.to_le_bytes());
71 }
72 }
73
74 fn unpack(src: &[u8], pos: &mut usize) -> StoreResult<Self> {
75 let n = read_u32(src, pos)? as usize;
76 let bytes = read_exact(src, pos, n.saturating_mul(8))?;
77 let mut bits = Vec::with_capacity(n);
78 for chunk in bytes.chunks_exact(8) {
79 bits.push(u64::from_le_bytes(chunk.try_into().unwrap()));
80 }
81 Ok(Self { bits })
82 }
83}
84
85#[derive(Debug, Default, Clone)]
87struct StrIntern {
88 to_id: HashMap<String, u32>,
89 values: Vec<Value>,
90}
91
92impl StrIntern {
93 fn intern(&mut self, s: String) -> u32 {
94 use std::collections::hash_map::Entry;
95 match self.to_id.entry(s) {
96 Entry::Occupied(e) => *e.get(),
97 Entry::Vacant(e) => {
98 let id = self.values.len() as u32;
99 let cloned = e.key().clone();
100 e.insert(id);
101 self.values.push(Value::Str(cloned));
102 id
103 }
104 }
105 }
106
107 fn get(&self, id: u32) -> &Value {
108 &self.values[id as usize]
109 }
110
111 fn pack(&self, out: &mut Vec<u8>) {
112 push_u32(out, self.values.len() as u32);
113 for v in &self.values {
114 let Value::Str(s) = v else {
115 unreachable!("StrIntern values are always Value::Str");
116 };
117 push_str(out, s);
118 }
119 }
120
121 fn unpack(src: &[u8], pos: &mut usize) -> StoreResult<Self> {
122 let n = read_u32(src, pos)? as usize;
123 let mut intern = Self::default();
124 intern.values.reserve(n);
125 intern.to_id.reserve(n);
126 for i in 0..n {
127 let s = read_str(src, pos)?;
128 intern.to_id.insert(s.clone(), i as u32);
129 intern.values.push(Value::Str(s));
130 }
131 Ok(intern)
132 }
133}
134
135fn grow<T: Clone>(data: &mut Vec<T>, adapter: &mut Vec<Value>, node: u32, fill: T, dummy: Value) {
136 let n = node as usize + 1;
137 if data.len() < n {
138 data.resize(n, fill);
139 adapter.resize(n, dummy);
140 }
141}
142
143#[derive(Debug, Clone)]
144enum Column {
145 Int {
146 data: Vec<i64>,
147 present: Bitmap,
148 adapter: Vec<Value>,
149 live: usize,
150 },
151 Float {
152 data: Vec<f64>,
153 present: Bitmap,
154 adapter: Vec<Value>,
155 live: usize,
156 },
157 Bool {
158 data: Vec<bool>,
159 present: Bitmap,
160 adapter: Vec<Value>,
161 live: usize,
162 },
163 Str {
164 ids: Vec<u32>,
165 present: Bitmap,
166 live: usize,
167 },
168 Mixed(HashMap<u32, Value>),
170}
171
172impl Column {
173 fn from_first(node: u32, value: Value, intern: &mut StrIntern) -> Self {
174 let mut col = match &value {
175 Value::Int(_) => Column::Int {
176 data: Vec::new(),
177 present: Bitmap::default(),
178 adapter: Vec::new(),
179 live: 0,
180 },
181 Value::Float(_) => Column::Float {
182 data: Vec::new(),
183 present: Bitmap::default(),
184 adapter: Vec::new(),
185 live: 0,
186 },
187 Value::Bool(_) => Column::Bool {
188 data: Vec::new(),
189 present: Bitmap::default(),
190 adapter: Vec::new(),
191 live: 0,
192 },
193 Value::Str(_) => Column::Str {
194 ids: Vec::new(),
195 present: Bitmap::default(),
196 live: 0,
197 },
198 Value::List(_) => Column::Mixed(HashMap::new()),
199 Value::Map(_) => Column::Mixed(HashMap::new()),
201 };
202 col.set(node, value, intern);
203 col
204 }
205
206 fn accepts(&self, value: &Value) -> bool {
207 matches!(
208 (self, value),
209 (Column::Int { .. }, Value::Int(_))
210 | (Column::Float { .. }, Value::Float(_))
211 | (Column::Bool { .. }, Value::Bool(_))
212 | (Column::Str { .. }, Value::Str(_))
213 | (Column::Mixed(_), _)
214 )
215 }
216
217 fn set(&mut self, node: u32, value: Value, intern: &mut StrIntern) {
218 if !self.accepts(&value) {
219 let mut map = self.take_map(intern);
220 map.insert(node, value);
221 *self = Column::Mixed(map);
222 return;
223 }
224 match (self, value) {
225 (
226 Column::Int {
227 data,
228 present,
229 adapter,
230 live,
231 },
232 Value::Int(v),
233 ) => {
234 grow(data, adapter, node, 0, Value::Int(0));
235 data[node as usize] = v;
236 adapter[node as usize] = Value::Int(v);
237 if present.set(node) {
238 *live += 1;
239 }
240 }
241 (
242 Column::Float {
243 data,
244 present,
245 adapter,
246 live,
247 },
248 Value::Float(v),
249 ) => {
250 grow(data, adapter, node, 0.0, Value::Float(0.0));
251 data[node as usize] = v;
252 adapter[node as usize] = Value::Float(v);
253 if present.set(node) {
254 *live += 1;
255 }
256 }
257 (
258 Column::Bool {
259 data,
260 present,
261 adapter,
262 live,
263 },
264 Value::Bool(v),
265 ) => {
266 grow(data, adapter, node, false, Value::Bool(false));
267 data[node as usize] = v;
268 adapter[node as usize] = Value::Bool(v);
269 if present.set(node) {
270 *live += 1;
271 }
272 }
273 (Column::Str { ids, present, live }, Value::Str(s)) => {
274 let id = intern.intern(s);
275 let n = node as usize + 1;
276 if ids.len() < n {
277 ids.resize(n, 0);
278 }
279 ids[node as usize] = id;
280 if present.set(node) {
281 *live += 1;
282 }
283 }
284 (Column::Mixed(map), v) => {
285 map.insert(node, v);
286 }
287 _ => unreachable!("accepts() rejected a matching type"),
288 }
289 }
290
291 fn get<'a>(&'a self, node: u32, intern: &'a StrIntern) -> Option<&'a Value> {
292 match self {
293 Column::Int {
294 present, adapter, ..
295 }
296 | Column::Float {
297 present, adapter, ..
298 }
299 | Column::Bool {
300 present, adapter, ..
301 } => {
302 if present.contains(node) {
303 Some(&adapter[node as usize])
304 } else {
305 None
306 }
307 }
308 Column::Str { ids, present, .. } => {
309 if present.contains(node) {
310 Some(intern.get(ids[node as usize]))
311 } else {
312 None
313 }
314 }
315 Column::Mixed(map) => map.get(&node),
316 }
317 }
318
319 fn remove(&mut self, node: u32, intern: &StrIntern) -> Option<Value> {
320 match self {
321 Column::Int {
322 data,
323 present,
324 live,
325 ..
326 } => {
327 if !present.clear(node) {
328 return None;
329 }
330 *live -= 1;
331 Some(Value::Int(data[node as usize]))
332 }
333 Column::Float {
334 data,
335 present,
336 live,
337 ..
338 } => {
339 if !present.clear(node) {
340 return None;
341 }
342 *live -= 1;
343 Some(Value::Float(data[node as usize]))
344 }
345 Column::Bool {
346 data,
347 present,
348 live,
349 ..
350 } => {
351 if !present.clear(node) {
352 return None;
353 }
354 *live -= 1;
355 Some(Value::Bool(data[node as usize]))
356 }
357 Column::Str { ids, present, live } => {
358 if !present.clear(node) {
359 return None;
360 }
361 *live -= 1;
362 Some(intern.get(ids[node as usize]).clone())
363 }
364 Column::Mixed(map) => map.remove(&node),
365 }
366 }
367
368 fn is_empty(&self) -> bool {
369 match self {
370 Column::Mixed(map) => map.is_empty(),
371 Column::Int { live, .. }
372 | Column::Float { live, .. }
373 | Column::Bool { live, .. }
374 | Column::Str { live, .. } => *live == 0,
375 }
376 }
377
378 fn take_map(&mut self, intern: &StrIntern) -> HashMap<u32, Value> {
379 match std::mem::replace(self, Column::Mixed(HashMap::new())) {
380 Column::Mixed(map) => map,
381 other => other.to_map(intern),
382 }
383 }
384
385 fn pack(&self, intern: &StrIntern, out: &mut Vec<u8>) {
386 match self {
387 Column::Int { data, present, .. } => {
388 out.push(0);
389 push_i64s(out, data);
390 present.pack(out);
391 }
392 Column::Float { data, present, .. } => {
393 out.push(1);
394 push_f64s(out, data);
395 present.pack(out);
396 }
397 Column::Bool { data, present, .. } => {
398 out.push(2);
399 push_u32(out, data.len() as u32);
400 out.reserve(data.len());
401 for b in data {
402 out.push(u8::from(*b));
403 }
404 present.pack(out);
405 }
406 Column::Str { ids, present, .. } => {
407 out.push(3);
408 push_u32s(out, ids);
409 present.pack(out);
410 }
411 Column::Mixed(map) => {
412 out.push(4);
413 let blob = bincode::serialize(map).expect("mixed column serialize cannot fail");
414 push_u32(out, blob.len() as u32);
415 out.extend_from_slice(&blob);
416 }
417 }
418 let _ = intern;
419 }
420
421 fn unpack(src: &[u8], pos: &mut usize) -> StoreResult<Self> {
422 let tag = *read_exact(src, pos, 1)?.first().unwrap();
423 match tag {
424 0 => {
425 let data = read_i64s(src, pos)?;
426 let present = Bitmap::unpack(src, pos)?;
427 let live = present.live_count();
428 let adapter = data.iter().copied().map(Value::Int).collect();
429 Ok(Column::Int {
430 data,
431 present,
432 adapter,
433 live,
434 })
435 }
436 1 => {
437 let data = read_f64s(src, pos)?;
438 let present = Bitmap::unpack(src, pos)?;
439 let live = present.live_count();
440 let adapter = data.iter().copied().map(Value::Float).collect();
441 Ok(Column::Float {
442 data,
443 present,
444 adapter,
445 live,
446 })
447 }
448 2 => {
449 let n = read_u32(src, pos)? as usize;
450 let bytes = read_exact(src, pos, n)?;
451 let data: Vec<bool> = bytes.iter().map(|&b| b != 0).collect();
452 let present = Bitmap::unpack(src, pos)?;
453 let live = present.live_count();
454 let adapter = data.iter().copied().map(Value::Bool).collect();
455 Ok(Column::Bool {
456 data,
457 present,
458 adapter,
459 live,
460 })
461 }
462 3 => {
463 let ids = read_u32s(src, pos)?;
464 let present = Bitmap::unpack(src, pos)?;
465 let live = present.live_count();
466 Ok(Column::Str { ids, present, live })
467 }
468 4 => {
469 let n = read_u32(src, pos)? as usize;
470 let blob = read_exact(src, pos, n)?;
471 let map: HashMap<u32, Value> =
472 bincode::deserialize(blob).map_err(|e| GraphError::Corrupt {
473 detail: format!("snapshot: mixed column: {e}"),
474 })?;
475 Ok(Column::Mixed(map))
476 }
477 other => Err(GraphError::Corrupt {
478 detail: format!("snapshot: unknown column tag {other}"),
479 }),
480 }
481 }
482
483 fn to_map(&self, intern: &StrIntern) -> HashMap<u32, Value> {
484 match self {
485 Column::Mixed(map) => map.clone(),
486 Column::Int {
487 data,
488 present,
489 live,
490 ..
491 } => {
492 let mut map = HashMap::with_capacity(*live);
493 present.for_each(|i| {
494 map.insert(i, Value::Int(data[i as usize]));
495 });
496 map
497 }
498 Column::Float {
499 data,
500 present,
501 live,
502 ..
503 } => {
504 let mut map = HashMap::with_capacity(*live);
505 present.for_each(|i| {
506 map.insert(i, Value::Float(data[i as usize]));
507 });
508 map
509 }
510 Column::Bool {
511 data,
512 present,
513 live,
514 ..
515 } => {
516 let mut map = HashMap::with_capacity(*live);
517 present.for_each(|i| {
518 map.insert(i, Value::Bool(data[i as usize]));
519 });
520 map
521 }
522 Column::Str { ids, present, live } => {
523 let mut map = HashMap::with_capacity(*live);
524 present.for_each(|i| {
525 map.insert(i, intern.get(ids[i as usize]).clone());
526 });
527 map
528 }
529 }
530 }
531}
532
533pub struct ColumnHandle<'a> {
539 col: Option<&'a Column>,
540 intern: &'a StrIntern,
541}
542
543impl<'a> ColumnHandle<'a> {
544 #[inline]
547 pub fn get(&self, node: u32) -> Option<&'a Value> {
548 self.col?.get(node, self.intern)
549 }
550}
551
552#[derive(Debug, Default, Clone)]
557pub struct ColumnStore {
558 cols: HashMap<String, Column>,
559 intern: StrIntern,
560}
561
562impl ColumnStore {
563 pub fn new() -> Self {
564 Self::default()
565 }
566
567 pub fn set(&mut self, node: u32, field: &str, value: Value) {
568 let ColumnStore { cols, intern } = self;
569 if let Some(col) = cols.get_mut(field) {
570 col.set(node, value, intern);
571 } else {
572 cols.insert(field.to_string(), Column::from_first(node, value, intern));
573 }
574 }
575
576 pub fn get(&self, node: u32, field: &str) -> Option<&Value> {
577 self.cols.get(field)?.get(node, &self.intern)
578 }
579
580 pub fn column(&self, field: &str) -> ColumnHandle<'_> {
586 ColumnHandle {
587 col: self.cols.get(field),
588 intern: &self.intern,
589 }
590 }
591
592 pub fn fields(&self) -> impl Iterator<Item = &str> {
593 self.cols.keys().map(|s| s.as_str())
594 }
595
596 pub fn remove(&mut self, node: u32, field: &str) -> Option<Value> {
599 let ColumnStore { cols, intern } = self;
600 let old = cols.get_mut(field)?.remove(node, intern)?;
601 if cols.get(field).is_some_and(Column::is_empty) {
602 cols.remove(field);
603 }
604 Some(old)
605 }
606
607 pub fn remove_all(&mut self, node: u32) {
612 let ColumnStore { cols, intern } = self;
613 cols.retain(|_, col| {
614 col.remove(node, intern);
615 !col.is_empty()
616 });
617 }
618
619 fn to_wire(&self) -> HashMap<String, HashMap<u32, Value>> {
620 let mut cols = HashMap::with_capacity(self.cols.len());
621 for (field, col) in &self.cols {
622 cols.insert(field.clone(), col.to_map(&self.intern));
623 }
624 cols
625 }
626
627 fn from_wire(cols: HashMap<String, HashMap<u32, Value>>) -> Self {
628 let mut store = Self::new();
629 for (field, values) in cols {
630 for (node, value) in values {
631 store.set(node, &field, value);
632 }
633 }
634 store
635 }
636
637 #[cfg(test)]
638 fn is_mixed(&self, field: &str) -> bool {
639 matches!(self.cols.get(field), Some(Column::Mixed(_)))
640 }
641
642 pub(crate) fn pack(&self, out: &mut Vec<u8>) {
644 self.intern.pack(out);
645 let mut fields: Vec<&String> = self.cols.keys().collect();
646 fields.sort();
647 push_u32(out, fields.len() as u32);
648 for f in fields {
649 push_str(out, f);
650 self.cols[f].pack(&self.intern, out);
651 }
652 }
653
654 pub(crate) fn unpack(src: &[u8]) -> StoreResult<(Self, usize)> {
655 let mut pos = 0usize;
656 let intern = StrIntern::unpack(src, &mut pos)?;
657 let n = read_u32(src, &mut pos)? as usize;
658 let mut cols = HashMap::with_capacity(n);
659 for _ in 0..n {
660 let name = read_str(src, &mut pos)?;
661 let col = Column::unpack(src, &mut pos)?;
662 cols.insert(name, col);
663 }
664 Ok((Self { cols, intern }, pos))
665 }
666}
667
668impl Serialize for ColumnStore {
669 fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
670 use serde::ser::SerializeStruct;
671 let mut state = serializer.serialize_struct("ColumnStore", 1)?;
672 state.serialize_field("cols", &self.to_wire())?;
673 state.end()
674 }
675}
676
677impl<'de> Deserialize<'de> for ColumnStore {
678 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
679 #[derive(Deserialize)]
680 struct Wire {
681 cols: HashMap<String, HashMap<u32, Value>>,
682 }
683 let wire = Wire::deserialize(deserializer)?;
684 Ok(Self::from_wire(wire.cols))
685 }
686}
687
688#[cfg(test)]
689mod tests {
690 use super::*;
691 use crate::types::Value;
692
693 #[test]
694 fn remove_returns_old_value_and_none_on_absent() {
695 let mut c = ColumnStore::new();
696 c.set(0, "name", Value::Str("ada".into()));
697 assert_eq!(c.remove(0, "name"), Some(Value::Str("ada".into())));
698 assert_eq!(c.get(0, "name"), None);
699 assert_eq!(c.remove(0, "name"), None);
701 assert_eq!(c.remove(99, "absent"), None);
703 }
704
705 #[test]
706 fn remove_prunes_empty_column_entry() {
707 let mut c = ColumnStore::new();
708 c.set(0, "x", Value::Int(1));
709 c.set(1, "x", Value::Int(2));
710 c.remove(0, "x");
711 assert!(c.fields().any(|f| f == "x"));
713 c.remove(1, "x");
714 assert!(!c.fields().any(|f| f == "x"));
716 }
717
718 #[test]
719 fn remove_all_clears_every_field_and_is_noop_on_absent() {
720 let mut c = ColumnStore::new();
721 c.set(0, "name", Value::Str("ada".into()));
722 c.set(0, "age", Value::Int(36));
723 c.set(1, "name", Value::Str("bob".into()));
724 c.remove_all(0);
725 assert_eq!(c.get(0, "name"), None);
726 assert_eq!(c.get(0, "age"), None);
727 assert_eq!(c.get(1, "name"), Some(&Value::Str("bob".into())));
728 c.remove_all(0);
730 assert_eq!(c.get(1, "name"), Some(&Value::Str("bob".into())));
731 c.remove_all(99);
732 assert_eq!(c.get(1, "name"), Some(&Value::Str("bob".into())));
733 }
734
735 #[test]
736 fn set_get_overwrite_and_sparse_nodes() {
737 let mut c = ColumnStore::new();
738 c.set(0, "name", Value::Str("ada".into()));
739 c.set(2, "name", Value::Str("bob".into())); c.set(0, "name", Value::Str("ada2".into())); c.set(0, "age", Value::Int(36));
742 assert_eq!(c.get(0, "name"), Some(&Value::Str("ada2".into())));
743 assert_eq!(c.get(1, "name"), None);
744 assert_eq!(c.get(2, "age"), None);
745 let mut fields: Vec<_> = c.fields().collect();
746 fields.sort();
747 assert_eq!(fields, vec!["age", "name"]);
748 }
749
750 #[test]
751 fn str_column_does_not_clone_on_get() {
752 let mut c = ColumnStore::new();
753 c.set(0, "name", Value::Str("ada".into()));
754 assert_eq!(c.get(0, "name"), Some(&Value::Str("ada".into())));
755 c.set(1, "name", Value::Str("ada".into()));
756 assert!(std::ptr::eq(
757 c.get(0, "name").unwrap(),
758 c.get(1, "name").unwrap()
759 ));
760 c.set(0, "title", Value::Str("ada".into()));
761 assert!(std::ptr::eq(
762 c.get(0, "name").unwrap(),
763 c.get(0, "title").unwrap()
764 ));
765 }
766
767 #[test]
768 fn mixed_type_column_round_trips() {
769 let mut c = ColumnStore::new();
770 c.set(0, "x", Value::Int(1));
771 c.set(1, "x", Value::Str("a".into()));
772 assert!(matches!(c.get(0, "x"), Some(&Value::Int(1))));
773 assert!(matches!(c.get(1, "x"), Some(&Value::Str(_))));
774 assert!(c.is_mixed("x"));
775 c.remove(1, "x");
776 assert!(c.is_mixed("x"));
777 assert_eq!(c.get(0, "x"), Some(&Value::Int(1)));
778 }
779
780 #[test]
781 fn list_and_type_change_spill_typed_scalars_stay_homogeneous() {
782 let mut c = ColumnStore::new();
783 c.set(0, "tags", Value::List(vec![Value::Int(1)]));
784 c.set(1, "tags", Value::List(vec![Value::Int(2)]));
785 assert!(c.is_mixed("tags"));
786 assert_eq!(c.get(0, "tags"), Some(&Value::List(vec![Value::Int(1)])));
787
788 c.set(0, "ok", Value::Bool(true));
789 c.set(1, "ok", Value::Bool(false));
790 assert!(!c.is_mixed("ok"));
791 assert_eq!(c.get(0, "ok"), Some(&Value::Bool(true)));
792
793 c.set(0, "score", Value::Float(1.5));
794 c.set(64, "score", Value::Float(2.5));
795 assert!(!c.is_mixed("score"));
796 assert_eq!(c.get(63, "score"), None);
797 assert_eq!(c.get(64, "score"), Some(&Value::Float(2.5)));
798
799 c.set(0, "n", Value::Int(1));
800 c.set(64, "n", Value::Int(2));
801 assert_eq!(c.get(0, "n"), Some(&Value::Int(1)));
802 assert_eq!(c.get(64, "n"), Some(&Value::Int(2)));
803
804 c.set(0, "flip", Value::Int(1));
805 c.set(0, "flip", Value::Str("a".into()));
806 assert!(c.is_mixed("flip"));
807 assert_eq!(c.get(0, "flip"), Some(&Value::Str("a".into())));
808 }
809
810 #[test]
811 fn column_handle_matches_get() {
812 let mut c = ColumnStore::new();
813 c.set(0, "name", Value::Str("ada".into()));
814 c.set(2, "age", Value::Int(36));
815 let name = c.column("name");
816 let age = c.column("age");
817 let missing = c.column("nope");
818 assert_eq!(name.get(0), c.get(0, "name"));
819 assert_eq!(name.get(1), None);
820 assert_eq!(age.get(2), Some(&Value::Int(36)));
821 assert_eq!(missing.get(0), None);
822 }
823
824 #[test]
825 fn serde_wire_is_nested_hashmap() {
826 #[derive(Serialize, Deserialize, PartialEq, Debug)]
827 struct Wire {
828 cols: HashMap<String, HashMap<u32, Value>>,
829 }
830
831 let mut cols = HashMap::new();
832 cols.insert("age".into(), HashMap::from([(0, Value::Int(30))]));
833 cols.insert(
834 "name".into(),
835 HashMap::from([(1, Value::Str("ada".into()))]),
836 );
837 cols.insert(
838 "mixed".into(),
839 HashMap::from([(0, Value::Int(1)), (1, Value::Str("x".into()))]),
840 );
841 cols.insert(
842 "tags".into(),
843 HashMap::from([(2, Value::List(vec![Value::Int(1)]))]),
844 );
845 let wire = Wire { cols };
846
847 let encoded = bincode::serialize(&wire).unwrap();
848 let store: ColumnStore = bincode::deserialize(&encoded).unwrap();
849 assert_eq!(store.get(0, "age"), Some(&Value::Int(30)));
850 assert_eq!(store.get(1, "name"), Some(&Value::Str("ada".into())));
851 assert_eq!(store.get(0, "mixed"), Some(&Value::Int(1)));
852 assert_eq!(store.get(1, "mixed"), Some(&Value::Str("x".into())));
853 assert_eq!(
854 store.get(2, "tags"),
855 Some(&Value::List(vec![Value::Int(1)]))
856 );
857 assert!(store.is_mixed("mixed"));
858 assert!(store.is_mixed("tags"));
859 assert!(!store.is_mixed("age"));
860 assert!(!store.is_mixed("name"));
861
862 let roundtrip: Wire = bincode::deserialize(&bincode::serialize(&store).unwrap()).unwrap();
863 assert_eq!(roundtrip.cols["age"][&0], Value::Int(30));
864 assert_eq!(roundtrip.cols["name"][&1], Value::Str("ada".into()));
865 assert_eq!(roundtrip.cols["mixed"][&0], Value::Int(1));
866 assert_eq!(roundtrip.cols["mixed"][&1], Value::Str("x".into()));
867 assert_eq!(roundtrip.cols["tags"][&2], Value::List(vec![Value::Int(1)]));
868 }
869
870 #[test]
871 fn pack_roundtrip_typed_mixed_and_intern() {
872 let mut c = ColumnStore::new();
873 c.set(0, "name", Value::Str("ada".into()));
874 c.set(1, "name", Value::Str("ada".into()));
875 c.set(0, "age", Value::Int(36));
876 c.set(0, "ok", Value::Bool(true));
877 c.set(2, "score", Value::Float(1.5));
878 c.set(0, "mix", Value::Int(1));
879 c.set(1, "mix", Value::Str("x".into()));
880 c.set(3, "tags", Value::List(vec![Value::Int(1)]));
881 let mut buf = Vec::new();
882 c.pack(&mut buf);
883 let (back, consumed) = ColumnStore::unpack(&buf).unwrap();
884 assert_eq!(consumed, buf.len());
885 assert_eq!(back.get(0, "name"), Some(&Value::Str("ada".into())));
886 assert!(std::ptr::eq(
887 back.get(0, "name").unwrap(),
888 back.get(1, "name").unwrap()
889 ));
890 assert_eq!(back.get(0, "age"), Some(&Value::Int(36)));
891 assert_eq!(back.get(0, "ok"), Some(&Value::Bool(true)));
892 assert_eq!(back.get(2, "score"), Some(&Value::Float(1.5)));
893 assert_eq!(back.get(0, "mix"), Some(&Value::Int(1)));
894 assert_eq!(back.get(1, "mix"), Some(&Value::Str("x".into())));
895 assert_eq!(back.get(3, "tags"), Some(&Value::List(vec![Value::Int(1)])));
896 assert!(back.is_mixed("mix"));
897 assert!(back.is_mixed("tags"));
898 }
899}