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::{BTreeSet, 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 pub(crate) prop_tombstones: HashMap<u32, BTreeSet<String>>,
564}
565
566impl ColumnStore {
567 pub fn new() -> Self {
568 Self::default()
569 }
570
571 pub fn set(&mut self, node: u32, field: &str, value: Value) {
572 let ColumnStore { cols, intern, .. } = self;
573 if let Some(col) = cols.get_mut(field) {
574 col.set(node, value, intern);
575 } else {
576 cols.insert(field.to_string(), Column::from_first(node, value, intern));
577 }
578 }
579
580 pub fn get(&self, node: u32, field: &str) -> Option<&Value> {
581 self.cols.get(field)?.get(node, &self.intern)
582 }
583
584 pub fn column(&self, field: &str) -> ColumnHandle<'_> {
590 ColumnHandle {
591 col: self.cols.get(field),
592 intern: &self.intern,
593 }
594 }
595
596 pub fn fields(&self) -> impl Iterator<Item = &str> {
597 self.cols.keys().map(|s| s.as_str())
598 }
599
600 pub fn remove(&mut self, node: u32, field: &str) -> Option<Value> {
603 let ColumnStore { cols, intern, .. } = self;
604 let old = cols.get_mut(field)?.remove(node, intern)?;
605 if cols.get(field).is_some_and(Column::is_empty) {
606 cols.remove(field);
607 }
608 Some(old)
609 }
610
611 pub fn remove_all(&mut self, node: u32) {
616 let ColumnStore { cols, intern, .. } = self;
617 cols.retain(|_, col| {
618 col.remove(node, intern);
619 !col.is_empty()
620 });
621 }
622
623 pub(crate) fn is_tombstoned(&self, node: u32, field: &str) -> bool {
627 self.prop_tombstones
628 .get(&node)
629 .is_some_and(|fields| fields.contains(field))
630 }
631
632 pub fn record_prop_tombstone(&mut self, node: u32, field: &str) {
638 self.prop_tombstones
639 .entry(node)
640 .or_default()
641 .insert(field.to_string());
642 }
643
644 pub(crate) fn to_wire(&self) -> HashMap<String, HashMap<u32, Value>> {
645 let mut cols = HashMap::with_capacity(self.cols.len());
646 for (field, col) in &self.cols {
647 cols.insert(field.clone(), col.to_map(&self.intern));
648 }
649 cols
650 }
651
652 fn from_wire(cols: HashMap<String, HashMap<u32, Value>>) -> Self {
653 let mut store = Self::new();
654 for (field, values) in cols {
655 for (node, value) in values {
656 store.set(node, &field, value);
657 }
658 }
659 store
660 }
661
662 #[cfg(test)]
663 fn is_mixed(&self, field: &str) -> bool {
664 matches!(self.cols.get(field), Some(Column::Mixed(_)))
665 }
666
667 pub(crate) fn pack(&self, out: &mut Vec<u8>) {
669 self.intern.pack(out);
670 let mut fields: Vec<&String> = self.cols.keys().collect();
671 fields.sort();
672 push_u32(out, fields.len() as u32);
673 for f in fields {
674 push_str(out, f);
675 self.cols[f].pack(&self.intern, out);
676 }
677 }
678
679 pub(crate) fn unpack(src: &[u8]) -> StoreResult<(Self, usize)> {
680 let mut pos = 0usize;
681 let intern = StrIntern::unpack(src, &mut pos)?;
682 let n = read_u32(src, &mut pos)? as usize;
683 let mut cols = HashMap::with_capacity(n);
684 for _ in 0..n {
685 let name = read_str(src, &mut pos)?;
686 let col = Column::unpack(src, &mut pos)?;
687 cols.insert(name, col);
688 }
689 Ok((
690 Self {
691 cols,
692 intern,
693 prop_tombstones: HashMap::new(),
694 },
695 pos,
696 ))
697 }
698}
699
700impl Serialize for ColumnStore {
701 fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
702 use serde::ser::SerializeStruct;
703 let mut state = serializer.serialize_struct("ColumnStore", 1)?;
704 state.serialize_field("cols", &self.to_wire())?;
705 state.end()
706 }
707}
708
709impl<'de> Deserialize<'de> for ColumnStore {
710 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
711 #[derive(Deserialize)]
712 struct Wire {
713 cols: HashMap<String, HashMap<u32, Value>>,
714 }
715 let wire = Wire::deserialize(deserializer)?;
716 Ok(Self::from_wire(wire.cols))
717 }
718}
719
720#[cfg(test)]
721mod tests {
722 use super::*;
723 use crate::types::Value;
724
725 #[test]
726 fn remove_returns_old_value_and_none_on_absent() {
727 let mut c = ColumnStore::new();
728 c.set(0, "name", Value::Str("ada".into()));
729 assert_eq!(c.remove(0, "name"), Some(Value::Str("ada".into())));
730 assert_eq!(c.get(0, "name"), None);
731 assert_eq!(c.remove(0, "name"), None);
733 assert_eq!(c.remove(99, "absent"), None);
735 }
736
737 #[test]
738 fn remove_prunes_empty_column_entry() {
739 let mut c = ColumnStore::new();
740 c.set(0, "x", Value::Int(1));
741 c.set(1, "x", Value::Int(2));
742 c.remove(0, "x");
743 assert!(c.fields().any(|f| f == "x"));
745 c.remove(1, "x");
746 assert!(!c.fields().any(|f| f == "x"));
748 }
749
750 #[test]
751 fn remove_all_clears_every_field_and_is_noop_on_absent() {
752 let mut c = ColumnStore::new();
753 c.set(0, "name", Value::Str("ada".into()));
754 c.set(0, "age", Value::Int(36));
755 c.set(1, "name", Value::Str("bob".into()));
756 c.remove_all(0);
757 assert_eq!(c.get(0, "name"), None);
758 assert_eq!(c.get(0, "age"), None);
759 assert_eq!(c.get(1, "name"), Some(&Value::Str("bob".into())));
760 c.remove_all(0);
762 assert_eq!(c.get(1, "name"), Some(&Value::Str("bob".into())));
763 c.remove_all(99);
764 assert_eq!(c.get(1, "name"), Some(&Value::Str("bob".into())));
765 }
766
767 #[test]
768 fn set_get_overwrite_and_sparse_nodes() {
769 let mut c = ColumnStore::new();
770 c.set(0, "name", Value::Str("ada".into()));
771 c.set(2, "name", Value::Str("bob".into())); c.set(0, "name", Value::Str("ada2".into())); c.set(0, "age", Value::Int(36));
774 assert_eq!(c.get(0, "name"), Some(&Value::Str("ada2".into())));
775 assert_eq!(c.get(1, "name"), None);
776 assert_eq!(c.get(2, "age"), None);
777 let mut fields: Vec<_> = c.fields().collect();
778 fields.sort();
779 assert_eq!(fields, vec!["age", "name"]);
780 }
781
782 #[test]
783 fn str_column_does_not_clone_on_get() {
784 let mut c = ColumnStore::new();
785 c.set(0, "name", Value::Str("ada".into()));
786 assert_eq!(c.get(0, "name"), Some(&Value::Str("ada".into())));
787 c.set(1, "name", Value::Str("ada".into()));
788 assert!(std::ptr::eq(
789 c.get(0, "name").unwrap(),
790 c.get(1, "name").unwrap()
791 ));
792 c.set(0, "title", Value::Str("ada".into()));
793 assert!(std::ptr::eq(
794 c.get(0, "name").unwrap(),
795 c.get(0, "title").unwrap()
796 ));
797 }
798
799 #[test]
800 fn mixed_type_column_round_trips() {
801 let mut c = ColumnStore::new();
802 c.set(0, "x", Value::Int(1));
803 c.set(1, "x", Value::Str("a".into()));
804 assert!(matches!(c.get(0, "x"), Some(&Value::Int(1))));
805 assert!(matches!(c.get(1, "x"), Some(&Value::Str(_))));
806 assert!(c.is_mixed("x"));
807 c.remove(1, "x");
808 assert!(c.is_mixed("x"));
809 assert_eq!(c.get(0, "x"), Some(&Value::Int(1)));
810 }
811
812 #[test]
813 fn list_and_type_change_spill_typed_scalars_stay_homogeneous() {
814 let mut c = ColumnStore::new();
815 c.set(0, "tags", Value::List(vec![Value::Int(1)]));
816 c.set(1, "tags", Value::List(vec![Value::Int(2)]));
817 assert!(c.is_mixed("tags"));
818 assert_eq!(c.get(0, "tags"), Some(&Value::List(vec![Value::Int(1)])));
819
820 c.set(0, "ok", Value::Bool(true));
821 c.set(1, "ok", Value::Bool(false));
822 assert!(!c.is_mixed("ok"));
823 assert_eq!(c.get(0, "ok"), Some(&Value::Bool(true)));
824
825 c.set(0, "score", Value::Float(1.5));
826 c.set(64, "score", Value::Float(2.5));
827 assert!(!c.is_mixed("score"));
828 assert_eq!(c.get(63, "score"), None);
829 assert_eq!(c.get(64, "score"), Some(&Value::Float(2.5)));
830
831 c.set(0, "n", Value::Int(1));
832 c.set(64, "n", Value::Int(2));
833 assert_eq!(c.get(0, "n"), Some(&Value::Int(1)));
834 assert_eq!(c.get(64, "n"), Some(&Value::Int(2)));
835
836 c.set(0, "flip", Value::Int(1));
837 c.set(0, "flip", Value::Str("a".into()));
838 assert!(c.is_mixed("flip"));
839 assert_eq!(c.get(0, "flip"), Some(&Value::Str("a".into())));
840 }
841
842 #[test]
843 fn column_handle_matches_get() {
844 let mut c = ColumnStore::new();
845 c.set(0, "name", Value::Str("ada".into()));
846 c.set(2, "age", Value::Int(36));
847 let name = c.column("name");
848 let age = c.column("age");
849 let missing = c.column("nope");
850 assert_eq!(name.get(0), c.get(0, "name"));
851 assert_eq!(name.get(1), None);
852 assert_eq!(age.get(2), Some(&Value::Int(36)));
853 assert_eq!(missing.get(0), None);
854 }
855
856 #[test]
857 fn serde_wire_is_nested_hashmap() {
858 #[derive(Serialize, Deserialize, PartialEq, Debug)]
859 struct Wire {
860 cols: HashMap<String, HashMap<u32, Value>>,
861 }
862
863 let mut cols = HashMap::new();
864 cols.insert("age".into(), HashMap::from([(0, Value::Int(30))]));
865 cols.insert(
866 "name".into(),
867 HashMap::from([(1, Value::Str("ada".into()))]),
868 );
869 cols.insert(
870 "mixed".into(),
871 HashMap::from([(0, Value::Int(1)), (1, Value::Str("x".into()))]),
872 );
873 cols.insert(
874 "tags".into(),
875 HashMap::from([(2, Value::List(vec![Value::Int(1)]))]),
876 );
877 let wire = Wire { cols };
878
879 let encoded = bincode::serialize(&wire).unwrap();
880 let store: ColumnStore = bincode::deserialize(&encoded).unwrap();
881 assert_eq!(store.get(0, "age"), Some(&Value::Int(30)));
882 assert_eq!(store.get(1, "name"), Some(&Value::Str("ada".into())));
883 assert_eq!(store.get(0, "mixed"), Some(&Value::Int(1)));
884 assert_eq!(store.get(1, "mixed"), Some(&Value::Str("x".into())));
885 assert_eq!(
886 store.get(2, "tags"),
887 Some(&Value::List(vec![Value::Int(1)]))
888 );
889 assert!(store.is_mixed("mixed"));
890 assert!(store.is_mixed("tags"));
891 assert!(!store.is_mixed("age"));
892 assert!(!store.is_mixed("name"));
893
894 let roundtrip: Wire = bincode::deserialize(&bincode::serialize(&store).unwrap()).unwrap();
895 assert_eq!(roundtrip.cols["age"][&0], Value::Int(30));
896 assert_eq!(roundtrip.cols["name"][&1], Value::Str("ada".into()));
897 assert_eq!(roundtrip.cols["mixed"][&0], Value::Int(1));
898 assert_eq!(roundtrip.cols["mixed"][&1], Value::Str("x".into()));
899 assert_eq!(roundtrip.cols["tags"][&2], Value::List(vec![Value::Int(1)]));
900 }
901
902 #[test]
903 fn pack_roundtrip_typed_mixed_and_intern() {
904 let mut c = ColumnStore::new();
905 c.set(0, "name", Value::Str("ada".into()));
906 c.set(1, "name", Value::Str("ada".into()));
907 c.set(0, "age", Value::Int(36));
908 c.set(0, "ok", Value::Bool(true));
909 c.set(2, "score", Value::Float(1.5));
910 c.set(0, "mix", Value::Int(1));
911 c.set(1, "mix", Value::Str("x".into()));
912 c.set(3, "tags", Value::List(vec![Value::Int(1)]));
913 let mut buf = Vec::new();
914 c.pack(&mut buf);
915 let (back, consumed) = ColumnStore::unpack(&buf).unwrap();
916 assert_eq!(consumed, buf.len());
917 assert_eq!(back.get(0, "name"), Some(&Value::Str("ada".into())));
918 assert!(std::ptr::eq(
919 back.get(0, "name").unwrap(),
920 back.get(1, "name").unwrap()
921 ));
922 assert_eq!(back.get(0, "age"), Some(&Value::Int(36)));
923 assert_eq!(back.get(0, "ok"), Some(&Value::Bool(true)));
924 assert_eq!(back.get(2, "score"), Some(&Value::Float(1.5)));
925 assert_eq!(back.get(0, "mix"), Some(&Value::Int(1)));
926 assert_eq!(back.get(1, "mix"), Some(&Value::Str("x".into())));
927 assert_eq!(back.get(3, "tags"), Some(&Value::List(vec![Value::Int(1)])));
928 assert!(back.is_mixed("mix"));
929 assert!(back.is_mixed("tags"));
930 }
931}