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())); }
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();
424 match tag {
425 0 => {
426 let data = read_i64s(src, pos)?;
427 let present = Bitmap::unpack(src, pos)?;
428 let live = present.live_count();
429 let adapter = data.iter().copied().map(Value::Int).collect();
430 Ok(Column::Int {
431 data,
432 present,
433 adapter,
434 live,
435 })
436 }
437 1 => {
438 let data = read_f64s(src, pos)?;
439 let present = Bitmap::unpack(src, pos)?;
440 let live = present.live_count();
441 let adapter = data.iter().copied().map(Value::Float).collect();
442 Ok(Column::Float {
443 data,
444 present,
445 adapter,
446 live,
447 })
448 }
449 2 => {
450 let n = read_u32(src, pos)? as usize;
451 let bytes = read_exact(src, pos, n)?;
452 let data: Vec<bool> = bytes.iter().map(|&b| b != 0).collect();
453 let present = Bitmap::unpack(src, pos)?;
454 let live = present.live_count();
455 let adapter = data.iter().copied().map(Value::Bool).collect();
456 Ok(Column::Bool {
457 data,
458 present,
459 adapter,
460 live,
461 })
462 }
463 3 => {
464 let ids = read_u32s(src, pos)?;
465 let present = Bitmap::unpack(src, pos)?;
466 let live = present.live_count();
467 Ok(Column::Str { ids, present, live })
468 }
469 4 => {
470 let n = read_u32(src, pos)? as usize;
471 let blob = read_exact(src, pos, n)?;
472 let map: HashMap<u32, Value> =
473 bincode::deserialize(blob).map_err(|e| GraphError::Corrupt {
474 detail: format!("snapshot: mixed column: {e}"),
475 })?;
476 Ok(Column::Mixed(map))
477 }
478 other => Err(GraphError::Corrupt {
479 detail: format!("snapshot: unknown column tag {other}"),
480 }),
481 }
482 }
483
484 fn to_map(&self, intern: &StrIntern) -> HashMap<u32, Value> {
485 match self {
486 Column::Mixed(map) => map.clone(),
487 Column::Int {
488 data,
489 present,
490 live,
491 ..
492 } => {
493 let mut map = HashMap::with_capacity(*live);
494 present.for_each(|i| {
495 map.insert(i, Value::Int(data[i as usize]));
496 });
497 map
498 }
499 Column::Float {
500 data,
501 present,
502 live,
503 ..
504 } => {
505 let mut map = HashMap::with_capacity(*live);
506 present.for_each(|i| {
507 map.insert(i, Value::Float(data[i as usize]));
508 });
509 map
510 }
511 Column::Bool {
512 data,
513 present,
514 live,
515 ..
516 } => {
517 let mut map = HashMap::with_capacity(*live);
518 present.for_each(|i| {
519 map.insert(i, Value::Bool(data[i as usize]));
520 });
521 map
522 }
523 Column::Str { ids, present, live } => {
524 let mut map = HashMap::with_capacity(*live);
525 present.for_each(|i| {
526 map.insert(i, intern.get(ids[i as usize]).clone());
527 });
528 map
529 }
530 }
531 }
532}
533
534pub struct ColumnHandle<'a> {
540 col: Option<&'a Column>,
541 intern: &'a StrIntern,
542}
543
544impl<'a> ColumnHandle<'a> {
545 #[inline]
548 pub fn get(&self, node: u32) -> Option<&'a Value> {
549 self.col?.get(node, self.intern)
550 }
551}
552
553#[derive(Debug, Default, Clone)]
558pub struct ColumnStore {
559 cols: HashMap<String, Column>,
560 intern: StrIntern,
561 pub(crate) prop_tombstones: HashMap<u32, BTreeSet<String>>,
565}
566
567impl ColumnStore {
568 pub fn new() -> Self {
569 Self::default()
570 }
571
572 pub fn set(&mut self, node: u32, field: &str, value: Value) {
573 let ColumnStore { cols, intern, .. } = self;
574 if let Some(col) = cols.get_mut(field) {
575 col.set(node, value, intern);
576 } else {
577 cols.insert(field.to_string(), Column::from_first(node, value, intern));
578 }
579 }
580
581 pub fn get(&self, node: u32, field: &str) -> Option<&Value> {
582 self.cols.get(field)?.get(node, &self.intern)
583 }
584
585 pub fn column(&self, field: &str) -> ColumnHandle<'_> {
591 ColumnHandle {
592 col: self.cols.get(field),
593 intern: &self.intern,
594 }
595 }
596
597 pub fn fields(&self) -> impl Iterator<Item = &str> {
598 self.cols.keys().map(|s| s.as_str())
599 }
600
601 pub fn remove(&mut self, node: u32, field: &str) -> Option<Value> {
604 let ColumnStore { cols, intern, .. } = self;
605 let old = cols.get_mut(field)?.remove(node, intern)?;
606 if cols.get(field).is_some_and(Column::is_empty) {
607 cols.remove(field);
608 }
609 Some(old)
610 }
611
612 pub fn remove_all(&mut self, node: u32) {
617 let ColumnStore { cols, intern, .. } = self;
618 cols.retain(|_, col| {
619 col.remove(node, intern);
620 !col.is_empty()
621 });
622 }
623
624 pub(crate) fn is_tombstoned(&self, node: u32, field: &str) -> bool {
628 self.prop_tombstones
629 .get(&node)
630 .is_some_and(|fields| fields.contains(field))
631 }
632
633 pub fn record_prop_tombstone(&mut self, node: u32, field: &str) {
639 self.prop_tombstones
640 .entry(node)
641 .or_default()
642 .insert(field.to_string());
643 }
644
645 pub(crate) fn to_wire(&self) -> HashMap<String, HashMap<u32, Value>> {
646 let mut cols = HashMap::with_capacity(self.cols.len());
647 for (field, col) in &self.cols {
648 cols.insert(field.clone(), col.to_map(&self.intern));
649 }
650 cols
651 }
652
653 fn from_wire(cols: HashMap<String, HashMap<u32, Value>>) -> Self {
654 let mut store = Self::new();
655 for (field, values) in cols {
656 for (node, value) in values {
657 store.set(node, &field, value);
658 }
659 }
660 store
661 }
662
663 #[cfg(test)]
664 fn is_mixed(&self, field: &str) -> bool {
665 matches!(self.cols.get(field), Some(Column::Mixed(_)))
666 }
667
668 pub(crate) fn pack(&self, out: &mut Vec<u8>) {
670 self.intern.pack(out);
671 let mut fields: Vec<&String> = self.cols.keys().collect();
672 fields.sort();
673 push_u32(out, fields.len() as u32);
674 for f in fields {
675 push_str(out, f);
676 self.cols[f].pack(&self.intern, out);
677 }
678 }
679
680 pub(crate) fn unpack(src: &[u8]) -> StoreResult<(Self, usize)> {
681 let mut pos = 0usize;
682 let intern = StrIntern::unpack(src, &mut pos)?;
683 let n = read_u32(src, &mut pos)? as usize;
684 let mut cols = HashMap::with_capacity(n);
685 for _ in 0..n {
686 let name = read_str(src, &mut pos)?;
687 let col = Column::unpack(src, &mut pos)?;
688 cols.insert(name, col);
689 }
690 Ok((
691 Self {
692 cols,
693 intern,
694 prop_tombstones: HashMap::new(),
695 },
696 pos,
697 ))
698 }
699}
700
701impl Serialize for ColumnStore {
702 fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
703 use serde::ser::SerializeStruct;
704 let mut state = serializer.serialize_struct("ColumnStore", 1)?;
705 state.serialize_field("cols", &self.to_wire())?;
706 state.end()
707 }
708}
709
710impl<'de> Deserialize<'de> for ColumnStore {
711 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
712 #[derive(Deserialize)]
713 struct Wire {
714 cols: HashMap<String, HashMap<u32, Value>>,
715 }
716 let wire = Wire::deserialize(deserializer)?;
717 Ok(Self::from_wire(wire.cols))
718 }
719}
720
721#[cfg(test)]
722mod tests {
723 use super::*;
724 use crate::types::Value;
725
726 #[test]
727 fn remove_returns_old_value_and_none_on_absent() {
728 let mut c = ColumnStore::new();
729 c.set(0, "name", Value::Str("ada".into()));
730 assert_eq!(c.remove(0, "name"), Some(Value::Str("ada".into())));
731 assert_eq!(c.get(0, "name"), None);
732 assert_eq!(c.remove(0, "name"), None);
734 assert_eq!(c.remove(99, "absent"), None);
736 }
737
738 #[test]
739 fn remove_prunes_empty_column_entry() {
740 let mut c = ColumnStore::new();
741 c.set(0, "x", Value::Int(1));
742 c.set(1, "x", Value::Int(2));
743 c.remove(0, "x");
744 assert!(c.fields().any(|f| f == "x"));
746 c.remove(1, "x");
747 assert!(!c.fields().any(|f| f == "x"));
749 }
750
751 #[test]
752 fn remove_all_clears_every_field_and_is_noop_on_absent() {
753 let mut c = ColumnStore::new();
754 c.set(0, "name", Value::Str("ada".into()));
755 c.set(0, "age", Value::Int(36));
756 c.set(1, "name", Value::Str("bob".into()));
757 c.remove_all(0);
758 assert_eq!(c.get(0, "name"), None);
759 assert_eq!(c.get(0, "age"), None);
760 assert_eq!(c.get(1, "name"), Some(&Value::Str("bob".into())));
761 c.remove_all(0);
763 assert_eq!(c.get(1, "name"), Some(&Value::Str("bob".into())));
764 c.remove_all(99);
765 assert_eq!(c.get(1, "name"), Some(&Value::Str("bob".into())));
766 }
767
768 #[test]
769 fn set_get_overwrite_and_sparse_nodes() {
770 let mut c = ColumnStore::new();
771 c.set(0, "name", Value::Str("ada".into()));
772 c.set(2, "name", Value::Str("bob".into())); c.set(0, "name", Value::Str("ada2".into())); c.set(0, "age", Value::Int(36));
775 assert_eq!(c.get(0, "name"), Some(&Value::Str("ada2".into())));
776 assert_eq!(c.get(1, "name"), None);
777 assert_eq!(c.get(2, "age"), None);
778 let mut fields: Vec<_> = c.fields().collect();
779 fields.sort();
780 assert_eq!(fields, vec!["age", "name"]);
781 }
782
783 #[test]
784 fn str_column_does_not_clone_on_get() {
785 let mut c = ColumnStore::new();
786 c.set(0, "name", Value::Str("ada".into()));
787 assert_eq!(c.get(0, "name"), Some(&Value::Str("ada".into())));
788 c.set(1, "name", Value::Str("ada".into()));
789 assert!(std::ptr::eq(
790 c.get(0, "name").unwrap(),
791 c.get(1, "name").unwrap()
792 ));
793 c.set(0, "title", Value::Str("ada".into()));
794 assert!(std::ptr::eq(
795 c.get(0, "name").unwrap(),
796 c.get(0, "title").unwrap()
797 ));
798 }
799
800 #[test]
801 fn mixed_type_column_round_trips() {
802 let mut c = ColumnStore::new();
803 c.set(0, "x", Value::Int(1));
804 c.set(1, "x", Value::Str("a".into()));
805 assert!(matches!(c.get(0, "x"), Some(&Value::Int(1))));
806 assert!(matches!(c.get(1, "x"), Some(&Value::Str(_))));
807 assert!(c.is_mixed("x"));
808 c.remove(1, "x");
809 assert!(c.is_mixed("x"));
810 assert_eq!(c.get(0, "x"), Some(&Value::Int(1)));
811 }
812
813 #[test]
814 fn list_and_type_change_spill_typed_scalars_stay_homogeneous() {
815 let mut c = ColumnStore::new();
816 c.set(0, "tags", Value::List(vec![Value::Int(1)]));
817 c.set(1, "tags", Value::List(vec![Value::Int(2)]));
818 assert!(c.is_mixed("tags"));
819 assert_eq!(c.get(0, "tags"), Some(&Value::List(vec![Value::Int(1)])));
820
821 c.set(0, "ok", Value::Bool(true));
822 c.set(1, "ok", Value::Bool(false));
823 assert!(!c.is_mixed("ok"));
824 assert_eq!(c.get(0, "ok"), Some(&Value::Bool(true)));
825
826 c.set(0, "score", Value::Float(1.5));
827 c.set(64, "score", Value::Float(2.5));
828 assert!(!c.is_mixed("score"));
829 assert_eq!(c.get(63, "score"), None);
830 assert_eq!(c.get(64, "score"), Some(&Value::Float(2.5)));
831
832 c.set(0, "n", Value::Int(1));
833 c.set(64, "n", Value::Int(2));
834 assert_eq!(c.get(0, "n"), Some(&Value::Int(1)));
835 assert_eq!(c.get(64, "n"), Some(&Value::Int(2)));
836
837 c.set(0, "flip", Value::Int(1));
838 c.set(0, "flip", Value::Str("a".into()));
839 assert!(c.is_mixed("flip"));
840 assert_eq!(c.get(0, "flip"), Some(&Value::Str("a".into())));
841 }
842
843 #[test]
844 fn column_handle_matches_get() {
845 let mut c = ColumnStore::new();
846 c.set(0, "name", Value::Str("ada".into()));
847 c.set(2, "age", Value::Int(36));
848 let name = c.column("name");
849 let age = c.column("age");
850 let missing = c.column("nope");
851 assert_eq!(name.get(0), c.get(0, "name"));
852 assert_eq!(name.get(1), None);
853 assert_eq!(age.get(2), Some(&Value::Int(36)));
854 assert_eq!(missing.get(0), None);
855 }
856
857 #[test]
858 fn serde_wire_is_nested_hashmap() {
859 #[derive(Serialize, Deserialize, PartialEq, Debug)]
860 struct Wire {
861 cols: HashMap<String, HashMap<u32, Value>>,
862 }
863
864 let mut cols = HashMap::new();
865 cols.insert("age".into(), HashMap::from([(0, Value::Int(30))]));
866 cols.insert(
867 "name".into(),
868 HashMap::from([(1, Value::Str("ada".into()))]),
869 );
870 cols.insert(
871 "mixed".into(),
872 HashMap::from([(0, Value::Int(1)), (1, Value::Str("x".into()))]),
873 );
874 cols.insert(
875 "tags".into(),
876 HashMap::from([(2, Value::List(vec![Value::Int(1)]))]),
877 );
878 let wire = Wire { cols };
879
880 let encoded = bincode::serialize(&wire).unwrap();
881 let store: ColumnStore = bincode::deserialize(&encoded).unwrap();
882 assert_eq!(store.get(0, "age"), Some(&Value::Int(30)));
883 assert_eq!(store.get(1, "name"), Some(&Value::Str("ada".into())));
884 assert_eq!(store.get(0, "mixed"), Some(&Value::Int(1)));
885 assert_eq!(store.get(1, "mixed"), Some(&Value::Str("x".into())));
886 assert_eq!(
887 store.get(2, "tags"),
888 Some(&Value::List(vec![Value::Int(1)]))
889 );
890 assert!(store.is_mixed("mixed"));
891 assert!(store.is_mixed("tags"));
892 assert!(!store.is_mixed("age"));
893 assert!(!store.is_mixed("name"));
894
895 let roundtrip: Wire = bincode::deserialize(&bincode::serialize(&store).unwrap()).unwrap();
896 assert_eq!(roundtrip.cols["age"][&0], Value::Int(30));
897 assert_eq!(roundtrip.cols["name"][&1], Value::Str("ada".into()));
898 assert_eq!(roundtrip.cols["mixed"][&0], Value::Int(1));
899 assert_eq!(roundtrip.cols["mixed"][&1], Value::Str("x".into()));
900 assert_eq!(roundtrip.cols["tags"][&2], Value::List(vec![Value::Int(1)]));
901 }
902
903 #[test]
904 fn pack_roundtrip_typed_mixed_and_intern() {
905 let mut c = ColumnStore::new();
906 c.set(0, "name", Value::Str("ada".into()));
907 c.set(1, "name", Value::Str("ada".into()));
908 c.set(0, "age", Value::Int(36));
909 c.set(0, "ok", Value::Bool(true));
910 c.set(2, "score", Value::Float(1.5));
911 c.set(0, "mix", Value::Int(1));
912 c.set(1, "mix", Value::Str("x".into()));
913 c.set(3, "tags", Value::List(vec![Value::Int(1)]));
914 let mut buf = Vec::new();
915 c.pack(&mut buf);
916 let (back, consumed) = ColumnStore::unpack(&buf).unwrap();
917 assert_eq!(consumed, buf.len());
918 assert_eq!(back.get(0, "name"), Some(&Value::Str("ada".into())));
919 assert!(std::ptr::eq(
920 back.get(0, "name").unwrap(),
921 back.get(1, "name").unwrap()
922 ));
923 assert_eq!(back.get(0, "age"), Some(&Value::Int(36)));
924 assert_eq!(back.get(0, "ok"), Some(&Value::Bool(true)));
925 assert_eq!(back.get(2, "score"), Some(&Value::Float(1.5)));
926 assert_eq!(back.get(0, "mix"), Some(&Value::Int(1)));
927 assert_eq!(back.get(1, "mix"), Some(&Value::Str("x".into())));
928 assert_eq!(back.get(3, "tags"), Some(&Value::List(vec![Value::Int(1)])));
929 assert!(back.is_mixed("mix"));
930 assert!(back.is_mixed("tags"));
931 }
932}