1use crate::columns::ColumnStore;
32use crate::edge_props::EdgeProps;
33use crate::idmap::IdMap;
34use crate::interner::Interner;
35use crate::snapshot::PerRuleIvfState;
36use crate::topology::Topology;
37use crate::types::{GraphError, Result, Value};
38use crate::v8::layout::{
39 ColumnData, ColumnsData, CsrAdjMap, CsrData, CsrEtype, CsrRow, EdgePropEntry, EdgePropsData,
40 FieldEntry, HnswRuleEntry, HnswSectionData, IdMapData, InternerData, ProvenanceEntry,
41 ProvenanceSectionData, RuleFireEntry, RuleTripEntry, RulesMetaData, StringTableData, Triple,
42 ViewsSectionData,
43};
44use crate::v8::{
45 HEADER_SIZE, SECTION_COLUMNS, SECTION_EDGE_PROPS, SECTION_HNSW, SECTION_IDS, SECTION_IVF_STATE,
46 SECTION_LAST_CHANGE, SECTION_META, SECTION_PROVENANCE, SECTION_RULES_META, SECTION_STRINGS,
47 SECTION_SYMS, SECTION_TOPOLOGY, SECTION_VIEWS,
48};
49use serde::{Deserialize, Serialize};
50use std::collections::{BTreeMap, BTreeSet, HashMap};
51use std::io::Write;
52
53#[derive(Serialize, Deserialize, Default)]
69pub struct V8Meta {
70 pub labels: Vec<u32>,
72 pub wal_truncated: bool,
74 #[serde(skip)]
76 pub edge_props: EdgeProps,
77 #[serde(skip)]
78 pub rule_defs: Vec<Vec<u8>>,
79 #[serde(skip)]
80 pub provenance: BTreeMap<String, BTreeSet<(u32, u32, u32)>>,
81 #[serde(skip)]
82 pub rule_tripped: BTreeMap<String, bool>,
83 #[serde(skip)]
84 pub rule_fires: BTreeMap<String, u64>,
85 #[serde(skip)]
86 pub view_defs: Vec<Vec<u8>>,
87 #[serde(skip)]
88 pub hnsw: BTreeMap<String, (Vec<u8>, Vec<u8>)>,
89 #[serde(skip)]
93 pub ivf_bytes: Vec<u8>,
94 #[serde(skip)]
98 pub last_change: HashMap<u32, u64>,
99}
100
101#[allow(clippy::too_many_arguments)]
135pub fn encode_v8<W: Write>(
136 base_topo: Option<&crate::v8::layout::ArchivedCsr>,
137 base_cols: Option<&crate::v8::layout::ArchivedColumns>,
138 base_strings: Option<&crate::v8::layout::ArchivedStringTable>,
139 base_edge_props: Option<(&crate::v8::layout::ArchivedEdgeProps, &[u8])>,
140 base_provenance_raw: Option<&[u8]>,
141 topo: &Topology,
142 cols: &ColumnStore,
143 ids: &IdMap,
144 syms: &Interner,
145 meta: &V8Meta,
146 out: &mut W,
147) -> Result<()> {
148 let topo_bytes = match base_topo {
150 Some(archived_csr) => rkyv_encode(&topology_merge_to_csr(archived_csr, topo))?,
151 None => rkyv_encode(&topology_to_csr(topo))?,
152 };
153
154 let (cols_data, strings_data) = match base_cols {
157 Some(archived) => columns_merge_to_data(archived, base_strings, cols)?,
158 None => columnstore_to_data(cols)?,
159 };
160 let cols_bytes = rkyv_encode(&cols_data)?;
161 let strings_bytes = rkyv_encode(&strings_data)?;
162 drop(cols_data);
163 drop(strings_data);
164 let ids_bytes = rkyv_encode(&idmap_to_data(ids))?;
165 let syms_bytes = rkyv_encode(&interner_to_data(syms))?;
166 let meta_bytes = bincode::serialize(meta).map_err(|e| GraphError::Corrupt {
167 detail: format!("v8: meta bincode serialize: {e}"),
168 })?;
169
170 let edge_props_bytes_owned: Vec<u8>;
172 let edge_props_bytes: &[u8] = match base_edge_props {
173 Some((_archived, raw)) if meta.edge_props.is_clean() => {
174 raw
176 }
177 Some((archived, _)) => {
178 edge_props_bytes_owned =
180 rkyv_encode(&edge_props_merge_to_data(archived, &meta.edge_props))?;
181 &edge_props_bytes_owned
182 }
183 None => {
184 edge_props_bytes_owned = rkyv_encode(&edge_props_to_data(&meta.edge_props))?;
186 &edge_props_bytes_owned
187 }
188 };
189
190 let hnsw_bytes = rkyv_encode(&hnsw_to_data(&meta.hnsw))?;
191
192 let prov_bytes_owned: Vec<u8>;
194 let prov_bytes: &[u8] = match base_provenance_raw {
195 Some(raw) if meta.provenance.is_empty() => {
196 raw
199 }
200 _ => {
201 prov_bytes_owned = rkyv_encode(&provenance_to_data(&meta.provenance))?;
202 &prov_bytes_owned
203 }
204 };
205
206 let rules_meta_bytes = rkyv_encode(&rules_meta_to_data(
207 &meta.rule_defs,
208 &meta.rule_tripped,
209 &meta.rule_fires,
210 ))?;
211 let views_bytes = rkyv_encode(&ViewsSectionData {
212 view_defs: meta.view_defs.clone(),
213 })?;
214 let ivf_bytes: &[u8] = &meta.ivf_bytes;
217
218 let last_change_bytes_owned: Vec<u8> =
221 bincode::serialize(&meta.last_change).map_err(|e| GraphError::Corrupt {
222 detail: format!("v8: last_change bincode serialize: {e}"),
223 })?;
224 let last_change_bytes: &[u8] = &last_change_bytes_owned;
225
226 let sections: &[(u8, &[u8])] = &[
227 (SECTION_TOPOLOGY, &topo_bytes),
228 (SECTION_COLUMNS, &cols_bytes),
229 (SECTION_STRINGS, &strings_bytes),
233 (SECTION_IDS, &ids_bytes),
234 (SECTION_SYMS, &syms_bytes),
235 (SECTION_META, &meta_bytes),
236 (SECTION_EDGE_PROPS, edge_props_bytes),
237 (SECTION_HNSW, &hnsw_bytes),
238 (SECTION_PROVENANCE, prov_bytes),
239 (SECTION_RULES_META, &rules_meta_bytes),
240 (SECTION_VIEWS, &views_bytes),
241 (SECTION_IVF_STATE, ivf_bytes),
242 (SECTION_LAST_CHANGE, last_change_bytes),
243 ];
244 let n = sections.len();
245
246 let mut offsets = Vec::with_capacity(n);
248 let mut cur: u64 = HEADER_SIZE as u64;
249 for (_, bytes) in sections {
250 offsets.push(cur);
251 cur = align8(cur + bytes.len() as u64);
252 }
253
254 for (i, ((_, bytes), &offset)) in sections.iter().zip(offsets.iter()).enumerate() {
256 if offset > u32::MAX as u64 {
257 return Err(GraphError::Corrupt {
258 detail: format!("v8: section {i} offset {offset} exceeds u32"),
259 });
260 }
261 if bytes.len() > u32::MAX as usize {
262 return Err(GraphError::Corrupt {
263 detail: format!("v8: section {i} length {} exceeds u32", bytes.len()),
264 });
265 }
266 }
267
268 let mut header = vec![0u8; HEADER_SIZE];
270 header[0..4].copy_from_slice(b"GDB1");
271 header[4..6].copy_from_slice(&crate::snapshot::VERSION_9.to_le_bytes());
272 header[6..8].copy_from_slice(&(n as u16).to_le_bytes());
273
274 let mut pos = 8usize;
275 for ((section_id, bytes), &offset) in sections.iter().zip(offsets.iter()) {
276 let crc32 = crc32fast::hash(bytes);
277 header[pos] = *section_id;
278 header[pos + 1] = 0;
279 header[pos + 2] = 0;
280 header[pos + 3] = 0;
281 header[pos + 4..pos + 8].copy_from_slice(&(offset as u32).to_le_bytes());
282 header[pos + 8..pos + 12].copy_from_slice(&(bytes.len() as u32).to_le_bytes());
283 header[pos + 12..pos + 16].copy_from_slice(&crc32.to_le_bytes());
284 pos += 16;
285 }
286 let header_crc = crc32fast::hash(&header[0..pos]);
288 header[pos..pos + 4].copy_from_slice(&header_crc.to_le_bytes());
289 out.write_all(&header).map_err(GraphError::Io)?;
292
293 let zero_pad = [0u8; 8];
305 let last_idx = sections.len().saturating_sub(1);
306 for (i, ((_, bytes), &offset)) in sections.iter().zip(offsets.iter()).enumerate() {
307 out.write_all(bytes).map_err(GraphError::Io)?;
308 if i < last_idx {
309 let end = offset + bytes.len() as u64;
310 let next = align8(end);
311 let pad = (next - end) as usize;
312 if pad > 0 {
313 out.write_all(&zero_pad[..pad]).map_err(GraphError::Io)?;
314 }
315 }
316 }
317
318 Ok(())
319}
320
321fn align8(n: u64) -> u64 {
322 (n + 7) & !7
323}
324
325fn rkyv_encode<T>(value: &T) -> Result<Vec<u8>>
330where
331 T: for<'a> rkyv::Serialize<
332 rkyv::api::high::HighSerializer<
333 rkyv::util::AlignedVec,
334 rkyv::ser::allocator::ArenaHandle<'a>,
335 rkyv::rancor::Error,
336 >,
337 >,
338{
339 rkyv::api::high::to_bytes::<rkyv::rancor::Error>(value)
340 .map(|av| av.to_vec())
341 .map_err(|e| GraphError::Corrupt {
342 detail: format!("v8: rkyv encode: {e}"),
343 })
344}
345
346fn topology_to_csr(topo: &Topology) -> CsrData {
354 let mut etype_ids: Vec<u32> = topo.by_type.keys().copied().collect();
355 etype_ids.sort_unstable();
356
357 let etypes = etype_ids
358 .into_iter()
359 .map(|et| {
360 let adj = &topo.by_type[&et];
361 CsrEtype {
362 etype: et,
363 out_adj: adj_map_to_csr(&adj.out),
364 in_adj: adj_map_to_csr(&adj.inn),
365 }
366 })
367 .collect();
368
369 CsrData {
370 etypes,
371 edge_count: topo.edge_count(),
372 }
373}
374
375fn adj_map_to_csr(adj: &std::collections::HashMap<u32, crate::topology::AdjList>) -> CsrAdjMap {
378 let mut vertices: Vec<u32> = adj.keys().copied().collect();
379 vertices.sort_unstable();
380
381 let rows = vertices
382 .into_iter()
383 .filter_map(|v| {
384 let al = &adj[&v];
385 let neighbors = al.merged().into_owned();
386 if neighbors.is_empty() {
387 None
388 } else {
389 Some(CsrRow {
390 vertex: v,
391 neighbors,
392 })
393 }
394 })
395 .collect();
396
397 CsrAdjMap { rows }
398}
399
400fn topology_merge_to_csr(base: &crate::v8::layout::ArchivedCsr, overlay: &Topology) -> CsrData {
408 use std::collections::BTreeSet;
409
410 let mut etype_set: BTreeSet<u32> = overlay.by_type.keys().copied().collect();
412 for et in base.etypes.iter() {
413 etype_set.insert(u32::from(et.etype));
414 }
415
416 let etypes: Vec<CsrEtype> = etype_set
417 .into_iter()
418 .map(|et| {
419 let overlay_adj = overlay.by_type.get(&et);
420 let base_entry = base
421 .etypes
422 .binary_search_by_key(&et, |e| u32::from(e.etype))
423 .ok()
424 .map(|i| &base.etypes[i]);
425
426 let out_tombstones = overlay.out_tombstones.get(&et);
427 let in_tombstones = overlay.in_tombstones.get(&et);
428
429 let out_adj = merge_adj_map(
430 overlay_adj.map(|a| &a.out),
431 base_entry.map(|e| &e.out_adj),
432 out_tombstones,
433 );
434 let in_adj = merge_adj_map(
435 overlay_adj.map(|a| &a.inn),
436 base_entry.map(|e| &e.in_adj),
437 in_tombstones,
438 );
439
440 CsrEtype {
441 etype: et,
442 out_adj,
443 in_adj,
444 }
445 })
446 .collect();
447
448 let base_count = u64::from(base.edge_count);
450 let overlay_count = overlay.edge_count();
451 let tombstone_count: u64 = overlay
453 .out_tombstones
454 .values()
455 .flat_map(|m| m.values())
456 .map(|s| s.len() as u64)
457 .sum();
458 let edge_count = base_count.saturating_sub(tombstone_count) + overlay_count;
459
460 CsrData { etypes, edge_count }
461}
462
463fn merge_adj_map(
469 overlay_adj: Option<&std::collections::HashMap<u32, crate::topology::AdjList>>,
470 base_adj: Option<&crate::v8::layout::ArchivedCsrAdjMap>,
471 tombstones: Option<&std::collections::HashMap<u32, std::collections::BTreeSet<u32>>>,
472) -> CsrAdjMap {
473 use std::collections::BTreeSet;
474
475 let mut vertex_set: BTreeSet<u32> = BTreeSet::new();
476 if let Some(o) = overlay_adj {
477 vertex_set.extend(o.keys().copied());
478 }
479 if let Some(b) = base_adj {
480 for row in b.rows.iter() {
481 vertex_set.insert(u32::from(row.vertex));
482 }
483 }
484
485 let rows: Vec<CsrRow> = vertex_set
486 .into_iter()
487 .filter_map(|v| {
488 let overlay_nbrs: Vec<u32> = overlay_adj
489 .and_then(|o| o.get(&v))
490 .map(|al| al.merged().into_owned())
491 .unwrap_or_default();
492
493 let base_nbrs: Vec<u32> = base_adj
494 .and_then(|b| {
495 b.rows
496 .binary_search_by_key(&v, |r| u32::from(r.vertex))
497 .ok()
498 .map(|i| &b.rows[i])
499 })
500 .map(|row| row.neighbors.iter().map(|n| u32::from(*n)).collect())
501 .unwrap_or_default();
502
503 let filtered_base_nbrs: Vec<u32> = match tombstones.and_then(|t| t.get(&v)) {
505 None => base_nbrs,
506 Some(t) => base_nbrs.into_iter().filter(|n| !t.contains(n)).collect(),
507 };
508
509 let merged = merge_sorted_unique_vecs(&overlay_nbrs, &filtered_base_nbrs);
511 if merged.is_empty() {
512 None
513 } else {
514 Some(CsrRow {
515 vertex: v,
516 neighbors: merged,
517 })
518 }
519 })
520 .collect();
521
522 CsrAdjMap { rows }
523}
524
525fn merge_sorted_unique_vecs(a: &[u32], b: &[u32]) -> Vec<u32> {
527 let mut out = Vec::with_capacity(a.len() + b.len());
528 let mut ai = 0;
529 let mut bi = 0;
530 while ai < a.len() && bi < b.len() {
531 match a[ai].cmp(&b[bi]) {
532 std::cmp::Ordering::Less => {
533 out.push(a[ai]);
534 ai += 1;
535 }
536 std::cmp::Ordering::Greater => {
537 out.push(b[bi]);
538 bi += 1;
539 }
540 std::cmp::Ordering::Equal => {
541 out.push(a[ai]);
542 ai += 1;
543 bi += 1;
544 }
545 }
546 }
547 out.extend_from_slice(&a[ai..]);
548 out.extend_from_slice(&b[bi..]);
549 out
550}
551
552fn columns_merge_to_data(
566 base: &crate::v8::layout::ArchivedColumns,
567 base_strings: Option<&crate::v8::layout::ArchivedStringTable>,
568 overlay: &ColumnStore,
569) -> Result<(ColumnsData, StringTableData)> {
570 let mut merged = archived_to_columnstore(base, base_strings);
573 for (node, fields) in &overlay.prop_tombstones {
575 for field in fields {
576 merged.remove(*node, field);
577 }
578 }
579 for (field_name, node_map) in overlay.to_wire() {
581 for (node, value) in node_map {
582 merged.set(node, &field_name, value);
583 }
584 }
585 columnstore_to_data(&merged)
587}
588
589fn columnstore_to_data(store: &ColumnStore) -> Result<(ColumnsData, StringTableData)> {
594 let mut buf = Vec::new();
595 store.pack(&mut buf);
596 let (mut data, strings) = decode_all_columns(&buf)?;
597 for field in data.fields.iter_mut() {
600 if let ColumnData::Mixed(ref blob) = field.col {
601 let map: HashMap<u32, Value> =
602 bincode::deserialize(blob.as_slice()).unwrap_or_default();
603 if let Some(vec_col) = try_promote_to_vector(&map) {
604 field.col = vec_col;
605 }
606 }
607 }
608 Ok((data, strings))
609}
610
611fn try_promote_to_vector(map: &HashMap<u32, Value>) -> Option<ColumnData> {
617 if map.is_empty() {
618 return None;
619 }
620 let dim = map.values().next().and_then(|v| match v {
622 Value::List(items) => {
623 if items.iter().all(|i| matches!(i, Value::Float(_))) {
624 Some(items.len())
625 } else {
626 None
627 }
628 }
629 _ => None,
630 })?;
631 if dim == 0 {
632 return None;
633 }
634 let max_id = *map.keys().max().unwrap_or(&0) as usize;
636 for v in map.values() {
637 match v {
638 Value::List(items)
639 if items.len() == dim && items.iter().all(|i| matches!(i, Value::Float(_))) => {}
640 _ => return None,
641 }
642 }
643 let n_nodes = max_id + 1;
645 let mut data = vec![0.0f64; n_nodes * dim];
646 let mut present_words = vec![0u64; n_nodes.div_ceil(64)];
647 for (&id, v) in map {
648 let items = match v {
649 Value::List(items) => items,
650 _ => unreachable!(),
651 };
652 let start = id as usize * dim;
653 for (i, item) in items.iter().enumerate() {
654 if let Value::Float(f) = item {
655 data[start + i] = *f;
656 }
657 }
658 let word = id as usize / 64;
659 let bit = id as usize % 64;
660 present_words[word] |= 1u64 << bit;
661 }
662 Some(ColumnData::Vector {
663 dim: dim as u32,
664 data,
665 present: present_words,
666 })
667}
668
669fn decode_all_columns(buf: &[u8]) -> Result<(ColumnsData, StringTableData)> {
674 use crate::pack::{read_exact, read_f64s, read_i64s, read_str, read_u32, read_u32s};
675 let mut pos = 0usize;
676
677 let n_intern = read_u32(buf, &mut pos).map_err(|_| GraphError::Corrupt {
679 detail: "v8: columns pack: truncated intern-string count".into(),
680 })? as usize;
681 let mut intern_strings: Vec<String> = Vec::with_capacity(n_intern);
682 for i in 0..n_intern {
683 intern_strings.push(read_str(buf, &mut pos).map_err(|_| GraphError::Corrupt {
684 detail: format!("v8: columns pack: truncated intern-string[{i}]"),
685 })?);
686 }
687
688 let n_fields = read_u32(buf, &mut pos).map_err(|_| GraphError::Corrupt {
689 detail: "v8: columns pack: truncated field count".into(),
690 })? as usize;
691 let mut fields = Vec::with_capacity(n_fields);
692
693 for field_idx in 0..n_fields {
694 let fname = read_str(buf, &mut pos).map_err(|_| GraphError::Corrupt {
695 detail: format!("v8: columns pack: truncated field name at index {field_idx}"),
696 })?;
697 let tag = read_exact(buf, &mut pos, 1).map_err(|_| GraphError::Corrupt {
698 detail: format!("v8: column '{fname}' pack decode: truncated tag byte"),
699 })?[0];
700 let col = match tag {
701 0 => {
702 let data = read_i64s(buf, &mut pos).map_err(|_| GraphError::Corrupt {
703 detail: format!("v8: column '{fname}' pack decode: truncated Int data"),
704 })?;
705 let present = unpack_bitmap(buf, &mut pos).ok_or_else(|| GraphError::Corrupt {
706 detail: format!("v8: column '{fname}' pack decode: truncated Int bitmap"),
707 })?;
708 ColumnData::Int { data, present }
709 }
710 1 => {
711 let data = read_f64s(buf, &mut pos).map_err(|_| GraphError::Corrupt {
712 detail: format!("v8: column '{fname}' pack decode: truncated Float data"),
713 })?;
714 let present = unpack_bitmap(buf, &mut pos).ok_or_else(|| GraphError::Corrupt {
715 detail: format!("v8: column '{fname}' pack decode: truncated Float bitmap"),
716 })?;
717 ColumnData::Float { data, present }
718 }
719 2 => {
720 let n = read_u32(buf, &mut pos).map_err(|_| GraphError::Corrupt {
721 detail: format!("v8: column '{fname}' pack decode: truncated Bool length"),
722 })? as usize;
723 let raw = read_exact(buf, &mut pos, n)
724 .map_err(|_| GraphError::Corrupt {
725 detail: format!("v8: column '{fname}' pack decode: truncated Bool data"),
726 })?
727 .to_vec();
728 let present = unpack_bitmap(buf, &mut pos).ok_or_else(|| GraphError::Corrupt {
729 detail: format!("v8: column '{fname}' pack decode: truncated Bool bitmap"),
730 })?;
731 ColumnData::Bool { data: raw, present }
732 }
733 3 => {
734 let ids = read_u32s(buf, &mut pos).map_err(|_| GraphError::Corrupt {
735 detail: format!("v8: column '{fname}' pack decode: truncated Str ids"),
736 })?;
737 let present = unpack_bitmap(buf, &mut pos).ok_or_else(|| GraphError::Corrupt {
738 detail: format!("v8: column '{fname}' pack decode: truncated Str bitmap"),
739 })?;
740 ColumnData::Str {
744 ids,
745 present,
746 strings: Vec::new(),
747 }
748 }
749 4 => {
750 let blob_len = read_u32(buf, &mut pos).map_err(|_| GraphError::Corrupt {
751 detail: format!("v8: column '{fname}' pack decode: truncated Mixed length"),
752 })? as usize;
753 let blob = read_exact(buf, &mut pos, blob_len)
754 .map_err(|_| GraphError::Corrupt {
755 detail: format!("v8: column '{fname}' pack decode: truncated Mixed data"),
756 })?
757 .to_vec();
758 ColumnData::Mixed(blob)
759 }
760 other => {
761 return Err(GraphError::Corrupt {
762 detail: format!(
763 "v8: column '{fname}' pack decode: unknown tag byte {other} at field \
764 index {field_idx}"
765 ),
766 });
767 }
768 };
769 fields.push(FieldEntry { name: fname, col });
770 }
771
772 fields.sort_by(|a, b| a.name.cmp(&b.name));
773 Ok((
774 ColumnsData { fields },
775 StringTableData {
776 strings: intern_strings,
777 },
778 ))
779}
780
781fn unpack_bitmap(buf: &[u8], pos: &mut usize) -> Option<Vec<u64>> {
782 use crate::pack::{read_exact, read_u32};
783 let n = read_u32(buf, pos).ok()? as usize;
784 let bytes = read_exact(buf, pos, n.saturating_mul(8)).ok()?;
785 Some(
786 bytes
787 .chunks_exact(8)
788 .map(|c| u64::from_le_bytes(c.try_into().unwrap())) .collect(),
790 )
791}
792
793fn idmap_to_data(ids: &IdMap) -> IdMapData {
794 let to_key: Vec<String> = ids.all_keys().to_vec();
795 let tombstones: Vec<u32> = ids
796 .all_keys()
797 .iter()
798 .enumerate()
799 .filter_map(|(i, _)| {
800 let id = i as u32;
801 if ids.is_tombstoned(id) {
802 Some(id)
803 } else {
804 None
805 }
806 })
807 .collect();
808 IdMapData { to_key, tombstones }
809}
810
811fn interner_to_data(syms: &Interner) -> InternerData {
812 let n = syms.len();
813 let mut to_str = Vec::with_capacity(n);
814 for i in 0u32..n as u32 {
815 to_str.push(syms.resolve(i).unwrap_or("").to_string());
816 }
817 InternerData { to_str }
818}
819
820pub fn csr_to_topology(archived: &crate::v8::layout::ArchivedCsr) -> Topology {
826 let mut topo = Topology::new();
827 for et_entry in archived.etypes.iter() {
828 let et = u32::from(et_entry.etype);
829 for row in et_entry.out_adj.rows.iter() {
830 let src = u32::from(row.vertex);
831 for &nbr in row.neighbors.iter() {
832 let dst = u32::from(nbr);
833 topo.add_edge(et, src, dst);
834 }
835 }
836 }
837 topo
838}
839
840pub fn archived_to_columnstore(
847 archived: &crate::v8::layout::ArchivedColumns,
848 shared: Option<&crate::v8::layout::ArchivedStringTable>,
849) -> ColumnStore {
850 let mut store = ColumnStore::new();
851 for field in archived.fields.iter() {
852 let name: &str = field.name.as_str();
853 match &field.col {
854 crate::v8::layout::ArchivedColumnData::Int { data, present } => {
855 bitmap_for_each(present.as_slice(), |node| {
856 let idx = node as usize;
857 if idx < data.len() {
858 store.set(node, name, Value::Int(i64::from(data[idx])));
859 }
860 });
861 }
862 crate::v8::layout::ArchivedColumnData::Float { data, present } => {
863 bitmap_for_each(present.as_slice(), |node| {
864 let idx = node as usize;
865 if idx < data.len() {
866 store.set(node, name, Value::Float(f64::from(data[idx])));
867 }
868 });
869 }
870 crate::v8::layout::ArchivedColumnData::Bool { data, present } => {
871 bitmap_for_each(present.as_slice(), |node| {
872 let idx = node as usize;
873 if idx < data.len() {
874 store.set(node, name, Value::Bool(data[idx] != 0));
875 }
876 });
877 }
878 crate::v8::layout::ArchivedColumnData::Str {
879 ids,
880 present,
881 strings,
882 } => {
883 let table = match shared {
884 Some(t) => &t.strings,
885 None => strings,
886 };
887 let strings_vec: Vec<String> =
888 table.iter().map(|s| s.as_str().to_string()).collect();
889 bitmap_for_each(present.as_slice(), |node| {
890 let idx = node as usize;
891 if idx < ids.len() {
892 let sid = u32::from(ids[idx]) as usize;
893 if sid < strings_vec.len() {
894 store.set(node, name, Value::Str(strings_vec[sid].clone()));
895 }
896 }
897 });
898 }
899 crate::v8::layout::ArchivedColumnData::Mixed(blob) => {
900 let map: HashMap<u32, Value> =
901 bincode::deserialize(blob.as_slice()).unwrap_or_default();
902 for (node, v) in map {
903 store.set(node, name, v);
904 }
905 }
906 crate::v8::layout::ArchivedColumnData::Vector { dim, data, present } => {
907 let dim_val = u32::from(*dim) as usize;
908 bitmap_for_each(present.as_slice(), |node| {
909 let start = node as usize * dim_val;
910 let end = start + dim_val;
911 if end <= data.len() {
912 let floats: Vec<Value> = data[start..end]
913 .iter()
914 .map(|f| Value::Float(f64::from(*f)))
915 .collect();
916 store.set(node, name, Value::List(floats));
917 }
918 });
919 }
920 }
921 }
922 store
923}
924
925fn bitmap_for_each<F: FnMut(u32)>(words: &[rkyv::Archived<u64>], mut f: F) {
926 for (wi, word) in words.iter().enumerate() {
927 let mut w: u64 = u64::from(*word);
928 while w != 0 {
929 let bit = w.trailing_zeros();
930 f(wi as u32 * 64 + bit);
931 w &= w - 1;
932 }
933 }
934}
935
936pub fn archived_to_idmap(archived: &crate::v8::layout::ArchivedIdMap) -> IdMap {
938 let mut ids = IdMap::new();
939 let tombstoned: BTreeSet<u32> = archived.tombstones.iter().map(|t| u32::from(*t)).collect();
940 for (i, key) in archived.to_key.iter().enumerate() {
941 let s = key.as_str();
942 if tombstoned.contains(&(i as u32)) {
943 ids.get_or_insert(s);
944 ids.delete(s);
945 } else if !s.is_empty() {
946 ids.get_or_insert(s);
947 }
948 }
949 ids
950}
951
952pub fn archived_to_interner(archived: &crate::v8::layout::ArchivedInterner) -> Interner {
954 let mut syms = Interner::new();
955 for s in archived.to_str.iter() {
956 syms.intern(s.as_str());
957 }
958 syms
959}
960
961pub fn decode_meta(bytes: &[u8]) -> Result<V8Meta> {
963 bincode::deserialize(bytes).map_err(|e| GraphError::Corrupt {
964 detail: format!("v8: meta bincode deserialize: {e}"),
965 })
966}
967
968pub fn decode_last_change_bytes(bytes: &[u8]) -> HashMap<u32, u64> {
974 if bytes.is_empty() {
975 return HashMap::new();
976 }
977 bincode::deserialize(bytes).unwrap_or_default()
978}
979
980pub fn decode_ivf_bytes(bytes: &[u8]) -> BTreeMap<String, PerRuleIvfState> {
986 if bytes.is_empty() {
987 return BTreeMap::new();
988 }
989 bincode::deserialize(bytes).unwrap_or_default()
990}
991
992fn edge_props_to_data(ep: &EdgeProps) -> EdgePropsData {
999 let mut entries: Vec<EdgePropEntry> = ep
1000 .sorted_entries()
1001 .into_iter()
1002 .map(|(etype, src, dst, props)| {
1003 let props_blob = bincode::serialize(&props).unwrap_or_default();
1004 EdgePropEntry {
1005 etype,
1006 src,
1007 dst,
1008 props_blob,
1009 }
1010 })
1011 .collect();
1012 entries.sort_by_key(|e| (e.etype, e.src, e.dst));
1013 EdgePropsData { entries }
1014}
1015
1016fn edge_props_merge_to_data(
1027 base: &crate::v8::layout::ArchivedEdgeProps,
1028 overlay: &EdgeProps,
1029) -> EdgePropsData {
1030 use std::collections::BTreeSet as BSet;
1031
1032 let overlay_keys: BSet<(u32, u32, u32)> = overlay
1034 .sorted_entries()
1035 .iter()
1036 .map(|&(et, s, d, _)| (et, s, d))
1037 .collect();
1038 let tombstoned_keys: BSet<(u32, u32, u32)> = overlay.tombstoned_keys().collect();
1039
1040 let mut entries: Vec<EdgePropEntry> = Vec::new();
1041
1042 for entry in base.entries.iter() {
1044 let et = u32::from(entry.etype);
1045 let s = u32::from(entry.src);
1046 let d = u32::from(entry.dst);
1047 let key = (et, s, d);
1048 if tombstoned_keys.contains(&key) || overlay_keys.contains(&key) {
1049 continue;
1050 }
1051 entries.push(EdgePropEntry {
1052 etype: et,
1053 src: s,
1054 dst: d,
1055 props_blob: entry.props_blob.as_slice().to_vec(),
1056 });
1057 }
1058
1059 for (et, s, d, props) in overlay.sorted_entries() {
1061 let props_blob = bincode::serialize(props).unwrap_or_default();
1062 entries.push(EdgePropEntry {
1063 etype: et,
1064 src: s,
1065 dst: d,
1066 props_blob,
1067 });
1068 }
1069
1070 entries.sort_by_key(|e| (e.etype, e.src, e.dst));
1071 EdgePropsData { entries }
1072}
1073
1074fn hnsw_to_data(hnsw: &BTreeMap<String, (Vec<u8>, Vec<u8>)>) -> HnswSectionData {
1077 let mut rules: Vec<HnswRuleEntry> = hnsw
1078 .iter()
1079 .map(|(name, (src, dst))| HnswRuleEntry {
1080 name: name.clone(),
1081 src_blob: src.clone(),
1082 dst_blob: dst.clone(),
1083 })
1084 .collect();
1085 rules.sort_by(|a, b| a.name.cmp(&b.name));
1086 HnswSectionData { rules }
1087}
1088
1089fn provenance_to_data(prov: &BTreeMap<String, BTreeSet<(u32, u32, u32)>>) -> ProvenanceSectionData {
1092 let mut entries: Vec<ProvenanceEntry> = prov
1093 .iter()
1094 .map(|(rule, triples)| {
1095 let mut sorted: Vec<Triple> = triples
1096 .iter()
1097 .map(|&(etype, src, dst)| Triple { etype, src, dst })
1098 .collect();
1099 sorted.sort_by_key(|t| (t.etype, t.src, t.dst));
1101 ProvenanceEntry {
1102 rule: rule.clone(),
1103 triples: sorted,
1104 }
1105 })
1106 .collect();
1107 entries.sort_by(|a, b| a.rule.cmp(&b.rule));
1108 ProvenanceSectionData { entries }
1109}
1110
1111fn rules_meta_to_data(
1114 rule_defs: &[Vec<u8>],
1115 tripped: &BTreeMap<String, bool>,
1116 fires: &BTreeMap<String, u64>,
1117) -> RulesMetaData {
1118 let tripped_vec: Vec<RuleTripEntry> = tripped
1119 .iter()
1120 .map(|(rule, &t)| RuleTripEntry {
1121 rule: rule.clone(),
1122 tripped: t,
1123 })
1124 .collect();
1125 let fires_vec: Vec<RuleFireEntry> = fires
1126 .iter()
1127 .map(|(rule, &f)| RuleFireEntry {
1128 rule: rule.clone(),
1129 fires: f,
1130 })
1131 .collect();
1132 RulesMetaData {
1133 rule_defs: rule_defs.to_vec(),
1134 tripped: tripped_vec,
1135 fires: fires_vec,
1136 }
1137}
1138
1139pub fn archived_edge_props_to_owned(archived: &crate::v8::layout::ArchivedEdgeProps) -> EdgeProps {
1145 let mut ep = EdgeProps::new();
1146 for entry in archived.entries.iter() {
1147 let etype = u32::from(entry.etype);
1148 let src = u32::from(entry.src);
1149 let dst = u32::from(entry.dst);
1150 let props: std::collections::BTreeMap<String, Value> =
1151 bincode::deserialize(entry.props_blob.as_slice()).unwrap_or_default();
1152 for (field, value) in props {
1153 ep.set(etype, src, dst, &field, value);
1154 }
1155 }
1156 ep
1157}
1158
1159pub fn archived_provenance_to_owned(
1161 archived: &crate::v8::layout::ArchivedProvenance,
1162) -> BTreeMap<String, BTreeSet<(u32, u32, u32)>> {
1163 let mut map = BTreeMap::new();
1164 for entry in archived.entries.iter() {
1165 let rule = entry.rule.as_str().to_string();
1166 let triples: BTreeSet<(u32, u32, u32)> = entry
1167 .triples
1168 .iter()
1169 .map(|t| (u32::from(t.etype), u32::from(t.src), u32::from(t.dst)))
1170 .collect();
1171 map.insert(rule, triples);
1172 }
1173 map
1174}
1175
1176pub fn decode_provenance_bytes(
1182 bytes: &[u8],
1183) -> BTreeMap<String, std::collections::BTreeSet<(u32, u32, u32)>> {
1184 if bytes.is_empty() {
1185 return BTreeMap::new();
1186 }
1187 match rkyv::access::<crate::v8::layout::ArchivedProvenanceSectionData, rkyv::rancor::Error>(
1188 bytes,
1189 ) {
1190 Ok(archived) => archived_provenance_to_owned(archived),
1191 Err(_) => BTreeMap::new(),
1192 }
1193}
1194
1195pub type RulesMetaOwned = (Vec<Vec<u8>>, BTreeMap<String, bool>, BTreeMap<String, u64>);
1197
1198pub fn archived_rules_meta_to_owned(
1200 archived: &crate::v8::layout::ArchivedRulesMeta,
1201) -> RulesMetaOwned {
1202 let rule_defs: Vec<Vec<u8>> = archived
1203 .rule_defs
1204 .iter()
1205 .map(|b| b.as_slice().to_vec())
1206 .collect();
1207 let tripped: BTreeMap<String, bool> = archived
1208 .tripped
1209 .iter()
1210 .map(|e| (e.rule.as_str().to_string(), e.tripped))
1211 .collect();
1212 let fires: BTreeMap<String, u64> = archived
1213 .fires
1214 .iter()
1215 .map(|e| (e.rule.as_str().to_string(), u64::from(e.fires)))
1216 .collect();
1217 (rule_defs, tripped, fires)
1218}
1219
1220pub fn archived_views_to_owned(archived: &crate::v8::layout::ArchivedViews) -> Vec<Vec<u8>> {
1222 archived
1223 .view_defs
1224 .iter()
1225 .map(|b| b.as_slice().to_vec())
1226 .collect()
1227}
1228
1229pub fn archived_provenance_contains(
1236 archived: &crate::v8::layout::ArchivedProvenance,
1237 rule: &str,
1238 etype: u32,
1239 src: u32,
1240 dst: u32,
1241) -> bool {
1242 let entry = archived.entries.iter().find(|e| e.rule.as_str() == rule);
1243 let Some(entry) = entry else {
1244 return false;
1245 };
1246 entry
1248 .triples
1249 .binary_search_by(|t| {
1250 let te = u32::from(t.etype);
1251 let ts = u32::from(t.src);
1252 let td = u32::from(t.dst);
1253 (te, ts, td).cmp(&(etype, src, dst))
1254 })
1255 .is_ok()
1256}
1257
1258pub fn archived_hnsw_to_owned(
1260 archived: &crate::v8::layout::ArchivedHnsw,
1261) -> BTreeMap<String, (Vec<u8>, Vec<u8>)> {
1262 archived
1263 .rules
1264 .iter()
1265 .map(|e| {
1266 (
1267 e.name.as_str().to_string(),
1268 (
1269 e.src_blob.as_slice().to_vec(),
1270 e.dst_blob.as_slice().to_vec(),
1271 ),
1272 )
1273 })
1274 .collect()
1275}