1use std::collections::{HashMap, HashSet};
6use std::fs;
7use std::io::{Read, Write};
8use std::path::PathBuf;
9use anyhow::{Context, Result};
10use serde::de::DeserializeOwned;
11use serde::{Deserialize, Serialize};
12use shape_value::{EnumPayload, EnumValue, PrintResult, PrintSpan, Upvalue, ValueWord};
13
14use crate::event_queue::WaitCondition;
15use crate::hashing::{HashDigest, hash_bytes};
16use shape_ast::ast::{DataDateTimeRef, DateTimeExpr, EnumDef, TimeReference, TypeAnnotation};
17use shape_ast::data::Timeframe;
18
19use crate::data::DataFrame;
20use shape_value::datatable::DataTable;
21
22pub const SNAPSHOT_VERSION: u32 = 5;
35
36pub(crate) const DEFAULT_CHUNK_LEN: usize = 4096;
37pub(crate) const BYTE_CHUNK_LEN: usize = 256 * 1024;
38
39#[derive(Clone)]
41pub struct SnapshotStore {
42 root: PathBuf,
43}
44
45impl SnapshotStore {
46 pub fn new(root: impl Into<PathBuf>) -> Result<Self> {
47 let root = root.into();
48 fs::create_dir_all(root.join("blobs"))
49 .with_context(|| format!("failed to create snapshot blob dir at {}", root.display()))?;
50 fs::create_dir_all(root.join("snapshots"))
51 .with_context(|| format!("failed to create snapshot dir at {}", root.display()))?;
52 Ok(Self { root })
53 }
54
55 fn blob_path(&self, hash: &HashDigest) -> PathBuf {
56 self.root
57 .join("blobs")
58 .join(format!("{}.bin.zst", hash.hex()))
59 }
60
61 fn snapshot_path(&self, hash: &HashDigest) -> PathBuf {
62 self.root
63 .join("snapshots")
64 .join(format!("{}.bin.zst", hash.hex()))
65 }
66
67 pub fn put_blob(&self, data: &[u8]) -> Result<HashDigest> {
68 let hash = hash_bytes(data);
69 let path = self.blob_path(&hash);
70 if path.exists() {
71 return Ok(hash);
72 }
73 let compressed = zstd::stream::encode_all(data, 0)?;
74 let mut file = fs::File::create(&path)?;
75 file.write_all(&compressed)?;
76 Ok(hash)
77 }
78
79 pub fn get_blob(&self, hash: &HashDigest) -> Result<Vec<u8>> {
80 let path = self.blob_path(hash);
81 let mut file = fs::File::open(&path)
82 .with_context(|| format!("snapshot blob not found: {}", path.display()))?;
83 let mut buf = Vec::new();
84 file.read_to_end(&mut buf)?;
85 let decompressed = zstd::stream::decode_all(&buf[..])?;
86 Ok(decompressed)
87 }
88
89 pub fn put_struct<T: Serialize>(&self, value: &T) -> Result<HashDigest> {
90 let bytes = bincode::serialize(value)?;
91 self.put_blob(&bytes)
92 }
93
94 pub fn get_struct<T: for<'de> Deserialize<'de>>(&self, hash: &HashDigest) -> Result<T> {
95 let bytes = self.get_blob(hash)?;
96 Ok(bincode::deserialize(&bytes)?)
97 }
98
99 pub fn put_snapshot(&self, snapshot: &ExecutionSnapshot) -> Result<HashDigest> {
100 let bytes = bincode::serialize(snapshot)?;
101 let hash = hash_bytes(&bytes);
102 let path = self.snapshot_path(&hash);
103 if !path.exists() {
104 let compressed = zstd::stream::encode_all(&bytes[..], 0)?;
105 let mut file = fs::File::create(&path)?;
106 file.write_all(&compressed)?;
107 }
108 Ok(hash)
109 }
110
111 pub fn get_snapshot(&self, hash: &HashDigest) -> Result<ExecutionSnapshot> {
112 let path = self.snapshot_path(hash);
113 let mut file = fs::File::open(&path)
114 .with_context(|| format!("snapshot not found: {}", path.display()))?;
115 let mut buf = Vec::new();
116 file.read_to_end(&mut buf)?;
117 let decompressed = zstd::stream::decode_all(&buf[..])?;
118 Ok(bincode::deserialize(&decompressed)?)
119 }
120
121 pub fn list_snapshots(&self) -> Result<Vec<(HashDigest, ExecutionSnapshot)>> {
131 let snapshots_dir = self.root.join("snapshots");
132 if !snapshots_dir.exists() {
133 return Ok(Vec::new());
134 }
135 let mut results = Vec::new();
136 for entry in fs::read_dir(&snapshots_dir)? {
137 let entry = entry?;
138 let path = entry.path();
139 if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
140 if let Some(hex) = name.strip_suffix(".bin.zst") {
142 let hash = HashDigest::from_hex(hex);
143 match self.get_snapshot(&hash) {
144 Ok(snap) => results.push((hash, snap)),
145 Err(_) => continue, }
147 }
148 }
149 }
150 results.sort_by(|a, b| b.1.created_at_ms.cmp(&a.1.created_at_ms));
152 Ok(results)
153 }
154
155 pub fn delete_snapshot(&self, hash: &HashDigest) -> Result<()> {
157 let path = self.snapshot_path(hash);
158 fs::remove_file(&path)
159 .with_context(|| format!("failed to delete snapshot: {}", path.display()))?;
160 Ok(())
161 }
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct ExecutionSnapshot {
172 pub version: u32,
175 pub created_at_ms: i64,
176 pub semantic_hash: HashDigest,
177 pub context_hash: HashDigest,
178 pub vm_hash: Option<HashDigest>,
179 pub bytecode_hash: Option<HashDigest>,
180 #[serde(default)]
182 pub script_path: Option<String>,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct SemanticSnapshot {
187 pub exported_symbols: HashSet<String>,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct ContextSnapshot {
192 pub data_load_mode: crate::context::DataLoadMode,
193 pub data_cache: Option<DataCacheSnapshot>,
194 pub current_id: Option<String>,
195 pub current_row_index: usize,
196 pub variable_scopes: Vec<HashMap<String, VariableSnapshot>>,
197 pub reference_datetime: Option<chrono::DateTime<chrono::Utc>>,
198 pub current_timeframe: Option<Timeframe>,
199 pub base_timeframe: Option<Timeframe>,
200 pub date_range: Option<(chrono::DateTime<chrono::Utc>, chrono::DateTime<chrono::Utc>)>,
201 pub range_start: usize,
202 pub range_end: usize,
203 pub range_active: bool,
204 pub type_alias_registry: HashMap<String, TypeAliasRuntimeEntrySnapshot>,
205 pub enum_registry: HashMap<String, EnumDef>,
206 #[serde(default)]
207 pub struct_type_registry: HashMap<String, shape_ast::ast::StructTypeDef>,
208 pub suspension_state: Option<SuspensionStateSnapshot>,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct VariableSnapshot {
213 pub value: SerializableVMValue,
214 pub kind: shape_ast::ast::VarKind,
215 pub is_initialized: bool,
216 pub is_function_scoped: bool,
217 pub format_hint: Option<String>,
218 pub format_overrides: Option<HashMap<String, SerializableVMValue>>,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct TypeAliasRuntimeEntrySnapshot {
223 pub base_type: String,
224 pub overrides: Option<HashMap<String, SerializableVMValue>>,
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct SuspensionStateSnapshot {
229 pub waiting_for: WaitCondition,
230 pub resume_pc: usize,
231 pub saved_locals: Vec<SerializableVMValue>,
232 pub saved_stack: Vec<SerializableVMValue>,
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct VmSnapshot {
237 pub ip: usize,
238 pub stack: Vec<SerializableVMValue>,
239 pub locals: Vec<SerializableVMValue>,
240 pub module_bindings: Vec<SerializableVMValue>,
241 pub call_stack: Vec<SerializableCallFrame>,
242 pub loop_stack: Vec<SerializableLoopContext>,
243 pub timeframe_stack: Vec<Option<Timeframe>>,
244 pub exception_handlers: Vec<SerializableExceptionHandler>,
245 #[serde(default)]
248 pub ip_blob_hash: Option<[u8; 32]>,
249 #[serde(default)]
253 pub ip_local_offset: Option<usize>,
254 #[serde(default)]
257 pub ip_function_id: Option<u16>,
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct SerializableCallFrame {
262 pub return_ip: usize,
263 pub locals_base: usize,
264 pub locals_count: usize,
265 pub function_id: Option<u16>,
266 pub upvalues: Option<Vec<SerializableVMValue>>,
267 #[serde(default)]
271 pub blob_hash: Option<[u8; 32]>,
272 #[serde(default)]
276 pub local_ip: Option<usize>,
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct SerializableLoopContext {
281 pub start: usize,
282 pub end: usize,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct SerializableExceptionHandler {
287 pub catch_ip: usize,
288 pub stack_size: usize,
289 pub call_depth: usize,
290}
291
292#[derive(Debug, Clone, Serialize, Deserialize)]
293pub enum SerializableVMValue {
294 Int(i64),
295 Number(f64),
296 Decimal(rust_decimal::Decimal),
297 String(String),
298 Bool(bool),
299 None,
300 Some(Box<SerializableVMValue>),
301 Unit,
302 Timeframe(Timeframe),
303 Duration(shape_ast::ast::Duration),
304 Time(chrono::DateTime<chrono::FixedOffset>),
305 TimeSpan(i64), TimeReference(TimeReference),
307 DateTimeExpr(DateTimeExpr),
308 DataDateTimeRef(DataDateTimeRef),
309 Array(Vec<SerializableVMValue>),
310 Function(u16),
311 TypeAnnotation(TypeAnnotation),
312 TypeAnnotatedValue {
313 type_name: String,
314 value: Box<SerializableVMValue>,
315 },
316 Enum(EnumValueSnapshot),
317 Closure {
318 function_id: u16,
319 upvalues: Vec<SerializableVMValue>,
320 },
321 ModuleFunction(String),
322 TypedObject {
323 schema_id: u64,
324 slot_data: Vec<SerializableVMValue>,
326 heap_mask: u64,
327 },
328 Range {
329 start: Option<Box<SerializableVMValue>>,
330 end: Option<Box<SerializableVMValue>>,
331 inclusive: bool,
332 },
333 Ok(Box<SerializableVMValue>),
334 Err(Box<SerializableVMValue>),
335 PrintResult(PrintableSnapshot),
336 SimulationCall {
337 name: String,
338 params: HashMap<String, SerializableVMValue>,
339 },
340 FunctionRef {
341 name: String,
342 closure: Option<Box<SerializableVMValue>>,
343 },
344 DataReference {
345 datetime: chrono::DateTime<chrono::FixedOffset>,
346 id: String,
347 timeframe: Timeframe,
348 },
349 Future(u64),
350 DataTable(BlobRef),
351 TypedTable {
352 schema_id: u64,
353 table: BlobRef,
354 },
355 RowView {
356 schema_id: u64,
357 table: BlobRef,
358 row_idx: usize,
359 },
360 ColumnRef {
361 schema_id: u64,
362 table: BlobRef,
363 col_id: u32,
364 },
365 IndexedTable {
366 schema_id: u64,
367 table: BlobRef,
368 index_col: u32,
369 },
370 TypedArray {
372 element_kind: TypedArrayElementKind,
373 blob: BlobRef,
374 len: usize,
375 },
376 Matrix {
378 blob: BlobRef,
379 rows: u32,
380 cols: u32,
381 },
382 HashMap {
384 keys: Vec<SerializableVMValue>,
385 values: Vec<SerializableVMValue>,
386 },
387 SidecarRef {
391 sidecar_id: u32,
392 blob_kind: BlobKind,
393 original_hash: HashDigest,
394 meta_a: u32,
396 meta_b: u32,
398 },
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize)]
402pub struct EnumValueSnapshot {
403 pub enum_name: String,
404 pub variant: String,
405 pub payload: EnumPayloadSnapshot,
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize)]
409pub enum EnumPayloadSnapshot {
410 Unit,
411 Tuple(Vec<SerializableVMValue>),
412 Struct(Vec<(String, SerializableVMValue)>),
413}
414
415#[derive(Debug, Clone, Serialize, Deserialize)]
416pub struct PrintableSnapshot {
417 pub rendered: String,
418 pub spans: Vec<PrintSpanSnapshot>,
419}
420
421#[derive(Debug, Clone, Serialize, Deserialize)]
422pub enum PrintSpanSnapshot {
423 Literal {
424 text: String,
425 start: usize,
426 end: usize,
427 span_id: String,
428 },
429 Value {
430 text: String,
431 start: usize,
432 end: usize,
433 span_id: String,
434 variable_name: Option<String>,
435 raw_value: Box<SerializableVMValue>,
436 type_name: String,
437 current_format: String,
438 format_params: HashMap<String, SerializableVMValue>,
439 },
440}
441
442#[derive(Debug, Clone, Serialize, Deserialize)]
443pub struct BlobRef {
444 pub hash: HashDigest,
445 pub kind: BlobKind,
446}
447
448#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
450pub enum TypedArrayElementKind {
451 I8,
452 I16,
453 I32,
454 I64,
455 U8,
456 U16,
457 U32,
458 U64,
459 F32,
460 F64,
461 Bool,
462}
463
464#[derive(Debug, Clone, Serialize, Deserialize)]
465pub enum BlobKind {
466 DataTable,
467 TypedArray(TypedArrayElementKind),
469 Matrix,
471}
472
473#[derive(Debug, Clone, Serialize, Deserialize)]
474pub struct ChunkedBlob {
475 pub chunk_hashes: Vec<HashDigest>,
476 pub total_len: usize,
477 pub chunk_len: usize,
478}
479
480#[derive(Debug, Clone, Serialize, Deserialize)]
481pub struct SerializableDataTable {
482 pub ipc_chunks: ChunkedBlob,
483 pub type_name: Option<String>,
484 pub schema_id: Option<u32>,
485}
486
487#[derive(Debug, Clone, Serialize, Deserialize)]
488pub struct SerializableDataFrame {
489 pub id: String,
490 pub timeframe: Timeframe,
491 pub timestamps: ChunkedBlob,
492 pub columns: Vec<SerializableDataFrameColumn>,
493}
494
495#[derive(Debug, Clone, Serialize, Deserialize)]
496pub struct SerializableDataFrameColumn {
497 pub name: String,
498 pub values: ChunkedBlob,
499}
500
501#[derive(Debug, Clone, Serialize, Deserialize)]
502pub struct CacheKeySnapshot {
503 pub id: String,
504 pub timeframe: Timeframe,
505}
506
507#[derive(Debug, Clone, Serialize, Deserialize)]
508pub struct CachedDataSnapshot {
509 pub key: CacheKeySnapshot,
510 pub historical: SerializableDataFrame,
511 pub current_index: usize,
512}
513
514#[derive(Debug, Clone, Serialize, Deserialize)]
515pub struct LiveBufferSnapshot {
516 pub key: CacheKeySnapshot,
517 pub rows: ChunkedBlob,
518}
519
520#[derive(Debug, Clone, Serialize, Deserialize)]
521pub struct DataCacheSnapshot {
522 pub historical: Vec<CachedDataSnapshot>,
523 pub live_buffer: Vec<LiveBufferSnapshot>,
524}
525
526pub(crate) fn store_chunked_vec<T: Serialize>(
527 values: &[T],
528 chunk_len: usize,
529 store: &SnapshotStore,
530) -> Result<ChunkedBlob> {
531 let chunk_len = chunk_len.max(1);
532 if values.is_empty() {
533 return Ok(ChunkedBlob {
534 chunk_hashes: Vec::new(),
535 total_len: 0,
536 chunk_len,
537 });
538 }
539 let mut hashes = Vec::new();
540 for chunk in values.chunks(chunk_len) {
541 let bytes = bincode::serialize(chunk)?;
542 let hash = store.put_blob(&bytes)?;
543 hashes.push(hash);
544 }
545 Ok(ChunkedBlob {
546 chunk_hashes: hashes,
547 total_len: values.len(),
548 chunk_len,
549 })
550}
551
552pub(crate) fn load_chunked_vec<T: DeserializeOwned>(
553 chunked: &ChunkedBlob,
554 store: &SnapshotStore,
555) -> Result<Vec<T>> {
556 if chunked.total_len == 0 {
557 return Ok(Vec::new());
558 }
559 let mut out = Vec::with_capacity(chunked.total_len);
560 for hash in &chunked.chunk_hashes {
561 let bytes = store.get_blob(hash)?;
562 let chunk: Vec<T> = bincode::deserialize(&bytes)?;
563 out.extend(chunk);
564 }
565 out.truncate(chunked.total_len);
566 Ok(out)
567}
568
569pub fn store_chunked_bytes(data: &[u8], store: &SnapshotStore) -> Result<ChunkedBlob> {
571 if data.is_empty() {
572 return Ok(ChunkedBlob {
573 chunk_hashes: Vec::new(),
574 total_len: 0,
575 chunk_len: BYTE_CHUNK_LEN,
576 });
577 }
578 let mut hashes = Vec::new();
579 for chunk in data.chunks(BYTE_CHUNK_LEN) {
580 let hash = store.put_blob(chunk)?;
581 hashes.push(hash);
582 }
583 Ok(ChunkedBlob {
584 chunk_hashes: hashes,
585 total_len: data.len(),
586 chunk_len: BYTE_CHUNK_LEN,
587 })
588}
589
590pub fn load_chunked_bytes(chunked: &ChunkedBlob, store: &SnapshotStore) -> Result<Vec<u8>> {
592 if chunked.total_len == 0 {
593 return Ok(Vec::new());
594 }
595 let mut out = Vec::with_capacity(chunked.total_len);
596 for hash in &chunked.chunk_hashes {
597 let bytes = store.get_blob(hash)?;
598 out.extend_from_slice(&bytes);
599 }
600 out.truncate(chunked.total_len);
601 Ok(out)
602}
603
604fn bytes_as_slice<T: Copy>(bytes: &[u8]) -> &[T] {
609 let elem_size = std::mem::size_of::<T>();
610 assert!(
611 bytes.len() % elem_size == 0,
612 "byte slice length {} not a multiple of element size {}",
613 bytes.len(),
614 elem_size
615 );
616 let len = bytes.len() / elem_size;
617 unsafe { std::slice::from_raw_parts(bytes.as_ptr() as *const T, len) }
618}
619
620fn slice_as_bytes<T>(data: &[T]) -> &[u8] {
622 let byte_len = data.len() * std::mem::size_of::<T>();
623 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, byte_len) }
624}
625
626pub fn nanboxed_to_serializable(
634 nb: &ValueWord,
635 store: &SnapshotStore,
636) -> Result<SerializableVMValue> {
637 use shape_value::value_word::NanTag;
638
639 match nb.tag() {
640 NanTag::F64 => Ok(SerializableVMValue::Number(nb.as_f64().unwrap())),
641 NanTag::I48 => Ok(SerializableVMValue::Int(nb.as_i64().unwrap())),
642 NanTag::Bool => Ok(SerializableVMValue::Bool(nb.as_bool().unwrap())),
643 NanTag::None => Ok(SerializableVMValue::None),
644 NanTag::Unit => Ok(SerializableVMValue::Unit),
645 NanTag::Function => Ok(SerializableVMValue::Function(nb.as_function().unwrap())),
646 NanTag::ModuleFunction => Ok(SerializableVMValue::ModuleFunction(format!(
647 "native#{}",
648 nb.as_module_function().unwrap()
649 ))),
650 NanTag::Heap => {
651 let hv = nb.as_heap_ref().unwrap();
652 heap_value_to_serializable(hv, store)
653 }
654 NanTag::Ref => Ok(SerializableVMValue::None), }
656}
657
658fn heap_value_to_serializable(
660 hv: &shape_value::heap_value::HeapValue,
661 store: &SnapshotStore,
662) -> Result<SerializableVMValue> {
663 use shape_value::heap_value::HeapValue;
664
665 Ok(match hv {
666 HeapValue::String(s) => SerializableVMValue::String((**s).clone()),
667 HeapValue::Decimal(d) => SerializableVMValue::Decimal(*d),
668 HeapValue::BigInt(i) => SerializableVMValue::Int(*i),
669 HeapValue::Array(arr) => {
670 let mut out = Vec::with_capacity(arr.len());
671 for v in arr.iter() {
672 out.push(nanboxed_to_serializable(v, store)?);
673 }
674 SerializableVMValue::Array(out)
675 }
676 HeapValue::Closure {
677 function_id,
678 upvalues,
679 } => {
680 let mut ups = Vec::new();
681 for up in upvalues.iter() {
682 let nb = up.get();
683 ups.push(nanboxed_to_serializable(&nb, store)?);
684 }
685 SerializableVMValue::Closure {
686 function_id: *function_id,
687 upvalues: ups,
688 }
689 }
690 HeapValue::TypedObject {
691 schema_id,
692 slots,
693 heap_mask,
694 } => {
695 let mut slot_data = Vec::with_capacity(slots.len());
696 for i in 0..slots.len() {
697 if *heap_mask & (1u64 << i) != 0 {
698 let hv_inner = slots[i].as_heap_value();
699 slot_data.push(heap_value_to_serializable(hv_inner, store)?);
700 } else {
701 let nb = unsafe { ValueWord::clone_from_bits(slots[i].raw()) };
704 slot_data.push(nanboxed_to_serializable(&nb, store)?);
705 }
706 }
707 SerializableVMValue::TypedObject {
708 schema_id: *schema_id,
709 slot_data,
710 heap_mask: *heap_mask,
711 }
712 }
713 HeapValue::HostClosure(_)
714 | HeapValue::ExprProxy(_)
715 | HeapValue::FilterExpr(_)
716 | HeapValue::TaskGroup { .. }
717 | HeapValue::TraitObject { .. }
718 | HeapValue::ProjectedRef(_)
719 | HeapValue::NativeView(_) => {
720 return Err(anyhow::anyhow!(
721 "Cannot snapshot transient value: {}",
722 hv.type_name()
723 ));
724 }
725 HeapValue::Enum(ev) => SerializableVMValue::Enum(enum_to_snapshot(ev, store)?),
726 HeapValue::Some(inner) => {
727 SerializableVMValue::Some(Box::new(nanboxed_to_serializable(inner, store)?))
728 }
729 HeapValue::Ok(inner) => {
730 SerializableVMValue::Ok(Box::new(nanboxed_to_serializable(inner, store)?))
731 }
732 HeapValue::Err(inner) => {
733 SerializableVMValue::Err(Box::new(nanboxed_to_serializable(inner, store)?))
734 }
735 HeapValue::Range {
736 start,
737 end,
738 inclusive,
739 } => SerializableVMValue::Range {
740 start: match start {
741 Some(v) => Some(Box::new(nanboxed_to_serializable(v, store)?)),
742 None => None,
743 },
744 end: match end {
745 Some(v) => Some(Box::new(nanboxed_to_serializable(v, store)?)),
746 None => None,
747 },
748 inclusive: *inclusive,
749 },
750 HeapValue::Timeframe(tf) => SerializableVMValue::Timeframe(*tf),
751 HeapValue::Duration(d) => SerializableVMValue::Duration(d.clone()),
752 HeapValue::Time(t) => SerializableVMValue::Time(*t),
753 HeapValue::TimeSpan(span) => SerializableVMValue::TimeSpan(span.num_milliseconds()),
754 HeapValue::TimeReference(tr) => SerializableVMValue::TimeReference((**tr).clone()),
755 HeapValue::DateTimeExpr(expr) => SerializableVMValue::DateTimeExpr((**expr).clone()),
756 HeapValue::DataDateTimeRef(dref) => SerializableVMValue::DataDateTimeRef((**dref).clone()),
757 HeapValue::TypeAnnotation(ta) => SerializableVMValue::TypeAnnotation((**ta).clone()),
758 HeapValue::TypeAnnotatedValue { type_name, value } => {
759 SerializableVMValue::TypeAnnotatedValue {
760 type_name: type_name.clone(),
761 value: Box::new(nanboxed_to_serializable(value, store)?),
762 }
763 }
764 HeapValue::PrintResult(pr) => {
765 SerializableVMValue::PrintResult(print_result_to_snapshot(pr, store)?)
766 }
767 HeapValue::SimulationCall(data) => {
768 let mut out = HashMap::new();
769 for (k, v) in data.params.iter() {
770 out.insert(k.clone(), nanboxed_to_serializable(&v.clone(), store)?);
772 }
773 SerializableVMValue::SimulationCall {
774 name: data.name.clone(),
775 params: out,
776 }
777 }
778 HeapValue::FunctionRef { name, closure } => SerializableVMValue::FunctionRef {
779 name: name.clone(),
780 closure: match closure {
781 Some(c) => Some(Box::new(nanboxed_to_serializable(c, store)?)),
782 None => None,
783 },
784 },
785 HeapValue::DataReference(data) => SerializableVMValue::DataReference {
786 datetime: data.datetime,
787 id: data.id.clone(),
788 timeframe: data.timeframe,
789 },
790 HeapValue::Future(id) => SerializableVMValue::Future(*id),
791 HeapValue::DataTable(dt) => {
792 let ser = serialize_datatable(dt, store)?;
793 let hash = store.put_struct(&ser)?;
794 SerializableVMValue::DataTable(BlobRef {
795 hash,
796 kind: BlobKind::DataTable,
797 })
798 }
799 HeapValue::TypedTable { schema_id, table } => {
800 let ser = serialize_datatable(table, store)?;
801 let hash = store.put_struct(&ser)?;
802 SerializableVMValue::TypedTable {
803 schema_id: *schema_id,
804 table: BlobRef {
805 hash,
806 kind: BlobKind::DataTable,
807 },
808 }
809 }
810 HeapValue::RowView {
811 schema_id,
812 table,
813 row_idx,
814 } => {
815 let ser = serialize_datatable(table, store)?;
816 let hash = store.put_struct(&ser)?;
817 SerializableVMValue::RowView {
818 schema_id: *schema_id,
819 table: BlobRef {
820 hash,
821 kind: BlobKind::DataTable,
822 },
823 row_idx: *row_idx,
824 }
825 }
826 HeapValue::ColumnRef {
827 schema_id,
828 table,
829 col_id,
830 } => {
831 let ser = serialize_datatable(table, store)?;
832 let hash = store.put_struct(&ser)?;
833 SerializableVMValue::ColumnRef {
834 schema_id: *schema_id,
835 table: BlobRef {
836 hash,
837 kind: BlobKind::DataTable,
838 },
839 col_id: *col_id,
840 }
841 }
842 HeapValue::IndexedTable {
843 schema_id,
844 table,
845 index_col,
846 } => {
847 let ser = serialize_datatable(table, store)?;
848 let hash = store.put_struct(&ser)?;
849 SerializableVMValue::IndexedTable {
850 schema_id: *schema_id,
851 table: BlobRef {
852 hash,
853 kind: BlobKind::DataTable,
854 },
855 index_col: *index_col,
856 }
857 }
858 HeapValue::NativeScalar(v) => {
859 if let Some(i) = v.as_i64() {
860 SerializableVMValue::Int(i)
861 } else {
862 SerializableVMValue::Number(v.as_f64())
863 }
864 }
865 HeapValue::HashMap(d) => {
866 let mut out_keys = Vec::with_capacity(d.keys.len());
867 let mut out_values = Vec::with_capacity(d.values.len());
868 for k in d.keys.iter() {
869 out_keys.push(nanboxed_to_serializable(k, store)?);
870 }
871 for v in d.values.iter() {
872 out_values.push(nanboxed_to_serializable(v, store)?);
873 }
874 SerializableVMValue::HashMap {
875 keys: out_keys,
876 values: out_values,
877 }
878 }
879 HeapValue::Set(d) => {
880 let mut out = Vec::with_capacity(d.items.len());
881 for item in d.items.iter() {
882 out.push(nanboxed_to_serializable(item, store)?);
883 }
884 SerializableVMValue::Array(out)
885 }
886 HeapValue::Deque(d) => {
887 let mut out = Vec::with_capacity(d.items.len());
888 for item in d.items.iter() {
889 out.push(nanboxed_to_serializable(item, store)?);
890 }
891 SerializableVMValue::Array(out)
892 }
893 HeapValue::PriorityQueue(d) => {
894 let mut out = Vec::with_capacity(d.items.len());
895 for item in d.items.iter() {
896 out.push(nanboxed_to_serializable(item, store)?);
897 }
898 SerializableVMValue::Array(out)
899 }
900 HeapValue::Content(node) => SerializableVMValue::String(format!("{}", node)),
901 HeapValue::Instant(t) => {
902 SerializableVMValue::String(format!("<instant:{:?}>", t.elapsed()))
903 }
904 HeapValue::IoHandle(data) => {
905 let status = if data.is_open() { "open" } else { "closed" };
906 SerializableVMValue::String(format!("<io_handle:{}:{}>", data.path, status))
907 }
908 HeapValue::SharedCell(arc) => nanboxed_to_serializable(&arc.read().unwrap(), store)?,
909 HeapValue::IntArray(a) => {
910 let blob = store_chunked_bytes(slice_as_bytes(a.as_slice()), store)?;
911 let hash = store.put_struct(&blob)?;
912 SerializableVMValue::TypedArray {
913 element_kind: TypedArrayElementKind::I64,
914 blob: BlobRef {
915 hash,
916 kind: BlobKind::TypedArray(TypedArrayElementKind::I64),
917 },
918 len: a.len(),
919 }
920 }
921 HeapValue::FloatArray(a) => {
922 let blob = store_chunked_bytes(slice_as_bytes(a.as_slice()), store)?;
923 let hash = store.put_struct(&blob)?;
924 SerializableVMValue::TypedArray {
925 element_kind: TypedArrayElementKind::F64,
926 blob: BlobRef {
927 hash,
928 kind: BlobKind::TypedArray(TypedArrayElementKind::F64),
929 },
930 len: a.len(),
931 }
932 }
933 HeapValue::BoolArray(a) => {
934 let blob = store_chunked_bytes(slice_as_bytes(a.as_slice()), store)?;
935 let hash = store.put_struct(&blob)?;
936 SerializableVMValue::TypedArray {
937 element_kind: TypedArrayElementKind::Bool,
938 blob: BlobRef {
939 hash,
940 kind: BlobKind::TypedArray(TypedArrayElementKind::Bool),
941 },
942 len: a.len(),
943 }
944 }
945 HeapValue::I8Array(a) => {
946 let blob = store_chunked_bytes(slice_as_bytes(a.as_slice()), store)?;
947 let hash = store.put_struct(&blob)?;
948 SerializableVMValue::TypedArray {
949 element_kind: TypedArrayElementKind::I8,
950 blob: BlobRef {
951 hash,
952 kind: BlobKind::TypedArray(TypedArrayElementKind::I8),
953 },
954 len: a.len(),
955 }
956 }
957 HeapValue::I16Array(a) => {
958 let blob = store_chunked_bytes(slice_as_bytes(a.as_slice()), store)?;
959 let hash = store.put_struct(&blob)?;
960 SerializableVMValue::TypedArray {
961 element_kind: TypedArrayElementKind::I16,
962 blob: BlobRef {
963 hash,
964 kind: BlobKind::TypedArray(TypedArrayElementKind::I16),
965 },
966 len: a.len(),
967 }
968 }
969 HeapValue::I32Array(a) => {
970 let blob = store_chunked_bytes(slice_as_bytes(a.as_slice()), store)?;
971 let hash = store.put_struct(&blob)?;
972 SerializableVMValue::TypedArray {
973 element_kind: TypedArrayElementKind::I32,
974 blob: BlobRef {
975 hash,
976 kind: BlobKind::TypedArray(TypedArrayElementKind::I32),
977 },
978 len: a.len(),
979 }
980 }
981 HeapValue::U8Array(a) => {
982 let blob = store_chunked_bytes(slice_as_bytes(a.as_slice()), store)?;
983 let hash = store.put_struct(&blob)?;
984 SerializableVMValue::TypedArray {
985 element_kind: TypedArrayElementKind::U8,
986 blob: BlobRef {
987 hash,
988 kind: BlobKind::TypedArray(TypedArrayElementKind::U8),
989 },
990 len: a.len(),
991 }
992 }
993 HeapValue::U16Array(a) => {
994 let blob = store_chunked_bytes(slice_as_bytes(a.as_slice()), store)?;
995 let hash = store.put_struct(&blob)?;
996 SerializableVMValue::TypedArray {
997 element_kind: TypedArrayElementKind::U16,
998 blob: BlobRef {
999 hash,
1000 kind: BlobKind::TypedArray(TypedArrayElementKind::U16),
1001 },
1002 len: a.len(),
1003 }
1004 }
1005 HeapValue::U32Array(a) => {
1006 let blob = store_chunked_bytes(slice_as_bytes(a.as_slice()), store)?;
1007 let hash = store.put_struct(&blob)?;
1008 SerializableVMValue::TypedArray {
1009 element_kind: TypedArrayElementKind::U32,
1010 blob: BlobRef {
1011 hash,
1012 kind: BlobKind::TypedArray(TypedArrayElementKind::U32),
1013 },
1014 len: a.len(),
1015 }
1016 }
1017 HeapValue::U64Array(a) => {
1018 let blob = store_chunked_bytes(slice_as_bytes(a.as_slice()), store)?;
1019 let hash = store.put_struct(&blob)?;
1020 SerializableVMValue::TypedArray {
1021 element_kind: TypedArrayElementKind::U64,
1022 blob: BlobRef {
1023 hash,
1024 kind: BlobKind::TypedArray(TypedArrayElementKind::U64),
1025 },
1026 len: a.len(),
1027 }
1028 }
1029 HeapValue::F32Array(a) => {
1030 let blob = store_chunked_bytes(slice_as_bytes(a.as_slice()), store)?;
1031 let hash = store.put_struct(&blob)?;
1032 SerializableVMValue::TypedArray {
1033 element_kind: TypedArrayElementKind::F32,
1034 blob: BlobRef {
1035 hash,
1036 kind: BlobKind::TypedArray(TypedArrayElementKind::F32),
1037 },
1038 len: a.len(),
1039 }
1040 }
1041 HeapValue::Matrix(m) => {
1042 let raw_bytes = slice_as_bytes(m.data.as_slice());
1043 let blob = store_chunked_bytes(raw_bytes, store)?;
1044 let hash = store.put_struct(&blob)?;
1045 SerializableVMValue::Matrix {
1046 blob: BlobRef {
1047 hash,
1048 kind: BlobKind::Matrix,
1049 },
1050 rows: m.rows,
1051 cols: m.cols,
1052 }
1053 }
1054 HeapValue::FloatArraySlice {
1055 parent,
1056 offset,
1057 len,
1058 } => {
1059 let start = *offset as usize;
1061 let end = start + *len as usize;
1062 let owned: Vec<f64> = parent.data[start..end].to_vec();
1063 let blob = store_chunked_bytes(slice_as_bytes(&owned), store)?;
1064 let hash = store.put_struct(&blob)?;
1065 SerializableVMValue::TypedArray {
1066 element_kind: TypedArrayElementKind::F64,
1067 blob: BlobRef {
1068 hash,
1069 kind: BlobKind::TypedArray(TypedArrayElementKind::F64),
1070 },
1071 len: *len as usize,
1072 }
1073 }
1074 HeapValue::Char(c) => SerializableVMValue::String(c.to_string()),
1075 HeapValue::Iterator(_)
1076 | HeapValue::Generator(_)
1077 | HeapValue::Mutex(_)
1078 | HeapValue::Atomic(_)
1079 | HeapValue::Lazy(_)
1080 | HeapValue::Channel(_) => {
1081 return Err(anyhow::anyhow!(
1082 "Cannot snapshot transient value: {}",
1083 hv.type_name()
1084 ));
1085 }
1086 })
1087}
1088
1089pub fn serializable_to_nanboxed(
1095 value: &SerializableVMValue,
1096 store: &SnapshotStore,
1097) -> Result<ValueWord> {
1098 Ok(match value {
1099 SerializableVMValue::Int(i) => ValueWord::from_i64(*i),
1100 SerializableVMValue::Number(n) => ValueWord::from_f64(*n),
1101 SerializableVMValue::Decimal(d) => ValueWord::from_decimal(*d),
1102 SerializableVMValue::String(s) => ValueWord::from_string(std::sync::Arc::new(s.clone())),
1103 SerializableVMValue::Bool(b) => ValueWord::from_bool(*b),
1104 SerializableVMValue::None => ValueWord::none(),
1105 SerializableVMValue::Unit => ValueWord::unit(),
1106 SerializableVMValue::Function(f) => ValueWord::from_function(*f),
1107 SerializableVMValue::ModuleFunction(_name) => ValueWord::from_module_function(0),
1108 SerializableVMValue::Some(v) => ValueWord::from_some(serializable_to_nanboxed(v, store)?),
1109 SerializableVMValue::Ok(v) => ValueWord::from_ok(serializable_to_nanboxed(v, store)?),
1110 SerializableVMValue::Err(v) => ValueWord::from_err(serializable_to_nanboxed(v, store)?),
1111 SerializableVMValue::Timeframe(tf) => ValueWord::from_timeframe(*tf),
1112 SerializableVMValue::Duration(d) => ValueWord::from_duration(d.clone()),
1113 SerializableVMValue::Time(t) => ValueWord::from_time(*t),
1114 SerializableVMValue::TimeSpan(ms) => {
1115 ValueWord::from_timespan(chrono::Duration::milliseconds(*ms))
1116 }
1117 SerializableVMValue::TimeReference(tr) => ValueWord::from_time_reference(tr.clone()),
1118 SerializableVMValue::DateTimeExpr(expr) => ValueWord::from_datetime_expr(expr.clone()),
1119 SerializableVMValue::DataDateTimeRef(dref) => {
1120 ValueWord::from_data_datetime_ref(dref.clone())
1121 }
1122 SerializableVMValue::Array(arr) => {
1123 let mut out = Vec::with_capacity(arr.len());
1124 for v in arr.iter() {
1125 out.push(serializable_to_nanboxed(v, store)?);
1126 }
1127 ValueWord::from_array(std::sync::Arc::new(out))
1128 }
1129 SerializableVMValue::TypeAnnotation(ta) => ValueWord::from_type_annotation(ta.clone()),
1130 SerializableVMValue::TypeAnnotatedValue { type_name, value } => {
1131 ValueWord::from_type_annotated_value(
1132 type_name.clone(),
1133 serializable_to_nanboxed(value, store)?,
1134 )
1135 }
1136 SerializableVMValue::Range {
1137 start,
1138 end,
1139 inclusive,
1140 } => ValueWord::from_range(
1141 match start {
1142 Some(v) => Some(serializable_to_nanboxed(v, store)?),
1143 None => None,
1144 },
1145 match end {
1146 Some(v) => Some(serializable_to_nanboxed(v, store)?),
1147 None => None,
1148 },
1149 *inclusive,
1150 ),
1151 SerializableVMValue::Future(id) => ValueWord::from_future(*id),
1152 SerializableVMValue::FunctionRef { name, closure } => ValueWord::from_function_ref(
1153 name.clone(),
1154 match closure {
1155 Some(c) => Some(serializable_to_nanboxed(c, store)?),
1156 None => None,
1157 },
1158 ),
1159 SerializableVMValue::DataReference {
1160 datetime,
1161 id,
1162 timeframe,
1163 } => ValueWord::from_data_reference(*datetime, id.clone(), *timeframe),
1164 SerializableVMValue::DataTable(blob) => {
1165 let ser: SerializableDataTable = store.get_struct(&blob.hash)?;
1166 ValueWord::from_datatable(std::sync::Arc::new(deserialize_datatable(ser, store)?))
1167 }
1168 SerializableVMValue::TypedTable { schema_id, table } => {
1169 let ser: SerializableDataTable = store.get_struct(&table.hash)?;
1170 ValueWord::from_typed_table(
1171 *schema_id,
1172 std::sync::Arc::new(deserialize_datatable(ser, store)?),
1173 )
1174 }
1175 SerializableVMValue::RowView {
1176 schema_id,
1177 table,
1178 row_idx,
1179 } => {
1180 let ser: SerializableDataTable = store.get_struct(&table.hash)?;
1181 ValueWord::from_row_view(
1182 *schema_id,
1183 std::sync::Arc::new(deserialize_datatable(ser, store)?),
1184 *row_idx,
1185 )
1186 }
1187 SerializableVMValue::ColumnRef {
1188 schema_id,
1189 table,
1190 col_id,
1191 } => {
1192 let ser: SerializableDataTable = store.get_struct(&table.hash)?;
1193 ValueWord::from_column_ref(
1194 *schema_id,
1195 std::sync::Arc::new(deserialize_datatable(ser, store)?),
1196 *col_id,
1197 )
1198 }
1199 SerializableVMValue::IndexedTable {
1200 schema_id,
1201 table,
1202 index_col,
1203 } => {
1204 let ser: SerializableDataTable = store.get_struct(&table.hash)?;
1205 ValueWord::from_indexed_table(
1206 *schema_id,
1207 std::sync::Arc::new(deserialize_datatable(ser, store)?),
1208 *index_col,
1209 )
1210 }
1211 SerializableVMValue::TypedArray {
1212 element_kind,
1213 blob,
1214 len,
1215 } => {
1216 let chunked: ChunkedBlob = store.get_struct(&blob.hash)?;
1217 let raw = load_chunked_bytes(&chunked, store)?;
1218 match element_kind {
1219 TypedArrayElementKind::I64 => {
1220 let data: Vec<i64> = bytes_as_slice::<i64>(&raw)[..*len].to_vec();
1221 ValueWord::from_int_array(std::sync::Arc::new(
1222 shape_value::TypedBuffer::from_vec(data),
1223 ))
1224 }
1225 TypedArrayElementKind::F64 => {
1226 let data: Vec<f64> = bytes_as_slice::<f64>(&raw)[..*len].to_vec();
1227 let aligned = shape_value::AlignedVec::from_vec(data);
1228 ValueWord::from_float_array(std::sync::Arc::new(
1229 shape_value::AlignedTypedBuffer::from_aligned(aligned),
1230 ))
1231 }
1232 TypedArrayElementKind::Bool => {
1233 let data: Vec<u8> = raw[..*len].to_vec();
1234 ValueWord::from_bool_array(std::sync::Arc::new(
1235 shape_value::TypedBuffer::from_vec(data),
1236 ))
1237 }
1238 TypedArrayElementKind::I8 => {
1239 let data: Vec<i8> = bytes_as_slice::<i8>(&raw)[..*len].to_vec();
1240 ValueWord::from_i8_array(std::sync::Arc::new(
1241 shape_value::TypedBuffer::from_vec(data),
1242 ))
1243 }
1244 TypedArrayElementKind::I16 => {
1245 let data: Vec<i16> = bytes_as_slice::<i16>(&raw)[..*len].to_vec();
1246 ValueWord::from_i16_array(std::sync::Arc::new(
1247 shape_value::TypedBuffer::from_vec(data),
1248 ))
1249 }
1250 TypedArrayElementKind::I32 => {
1251 let data: Vec<i32> = bytes_as_slice::<i32>(&raw)[..*len].to_vec();
1252 ValueWord::from_i32_array(std::sync::Arc::new(
1253 shape_value::TypedBuffer::from_vec(data),
1254 ))
1255 }
1256 TypedArrayElementKind::U8 => {
1257 let data: Vec<u8> = raw[..*len].to_vec();
1258 ValueWord::from_u8_array(std::sync::Arc::new(
1259 shape_value::TypedBuffer::from_vec(data),
1260 ))
1261 }
1262 TypedArrayElementKind::U16 => {
1263 let data: Vec<u16> = bytes_as_slice::<u16>(&raw)[..*len].to_vec();
1264 ValueWord::from_u16_array(std::sync::Arc::new(
1265 shape_value::TypedBuffer::from_vec(data),
1266 ))
1267 }
1268 TypedArrayElementKind::U32 => {
1269 let data: Vec<u32> = bytes_as_slice::<u32>(&raw)[..*len].to_vec();
1270 ValueWord::from_u32_array(std::sync::Arc::new(
1271 shape_value::TypedBuffer::from_vec(data),
1272 ))
1273 }
1274 TypedArrayElementKind::U64 => {
1275 let data: Vec<u64> = bytes_as_slice::<u64>(&raw)[..*len].to_vec();
1276 ValueWord::from_u64_array(std::sync::Arc::new(
1277 shape_value::TypedBuffer::from_vec(data),
1278 ))
1279 }
1280 TypedArrayElementKind::F32 => {
1281 let data: Vec<f32> = bytes_as_slice::<f32>(&raw)[..*len].to_vec();
1282 ValueWord::from_f32_array(std::sync::Arc::new(
1283 shape_value::TypedBuffer::from_vec(data),
1284 ))
1285 }
1286 }
1287 }
1288 SerializableVMValue::Matrix { blob, rows, cols } => {
1289 let chunked: ChunkedBlob = store.get_struct(&blob.hash)?;
1290 let raw = load_chunked_bytes(&chunked, store)?;
1291 let data: Vec<f64> = bytes_as_slice::<f64>(&raw).to_vec();
1292 let aligned = shape_value::AlignedVec::from_vec(data);
1293 let matrix = shape_value::heap_value::MatrixData::from_flat(aligned, *rows, *cols);
1294 ValueWord::from_matrix(std::sync::Arc::new(matrix))
1295 }
1296 SerializableVMValue::HashMap { keys, values } => {
1297 let mut k_out = Vec::with_capacity(keys.len());
1298 for k in keys.iter() {
1299 k_out.push(serializable_to_nanboxed(k, store)?);
1300 }
1301 let mut v_out = Vec::with_capacity(values.len());
1302 for v in values.iter() {
1303 v_out.push(serializable_to_nanboxed(v, store)?);
1304 }
1305 ValueWord::from_hashmap_pairs(k_out, v_out)
1306 }
1307 SerializableVMValue::SidecarRef { .. } => {
1308 return Err(anyhow::anyhow!(
1309 "SidecarRef must be reassembled before deserialization"
1310 ));
1311 }
1312 SerializableVMValue::Enum(ev) => {
1313 let enum_val = snapshot_to_enum(ev, store)?;
1315 enum_val
1316 }
1317 SerializableVMValue::Closure {
1318 function_id,
1319 upvalues,
1320 } => {
1321 let mut ups = Vec::new();
1322 for v in upvalues.iter() {
1323 ups.push(Upvalue::new(serializable_to_nanboxed(v, store)?));
1324 }
1325 ValueWord::from_heap_value(shape_value::heap_value::HeapValue::Closure {
1326 function_id: *function_id,
1327 upvalues: ups,
1328 })
1329 }
1330 SerializableVMValue::TypedObject {
1331 schema_id,
1332 slot_data,
1333 heap_mask,
1334 } => {
1335 let mut slots = Vec::with_capacity(slot_data.len());
1336 let mut new_heap_mask: u64 = 0;
1337 for (i, sv) in slot_data.iter().enumerate() {
1338 if *heap_mask & (1u64 << i) != 0 {
1339 let nb = serializable_to_nanboxed(sv, store)?;
1344 let (slot, is_heap) = crate::type_schema::nb_to_slot(&nb);
1345 slots.push(slot);
1346 if is_heap {
1347 new_heap_mask |= 1u64 << i;
1348 }
1349 } else {
1350 let n = match sv {
1352 SerializableVMValue::Number(n) => *n,
1353 _ => 0.0,
1354 };
1355 slots.push(shape_value::ValueSlot::from_number(n));
1356 }
1357 }
1358 ValueWord::from_heap_value(shape_value::heap_value::HeapValue::TypedObject {
1359 schema_id: *schema_id,
1360 slots: slots.into_boxed_slice(),
1361 heap_mask: new_heap_mask,
1362 })
1363 }
1364 SerializableVMValue::PrintResult(pr) => {
1365 let print_result = snapshot_to_print_result(pr, store)?;
1367 ValueWord::from_print_result(print_result)
1368 }
1369 SerializableVMValue::SimulationCall { name, params } => {
1370 let mut out = HashMap::new();
1372 for (k, v) in params.iter() {
1373 out.insert(k.clone(), serializable_to_nanboxed(v, store)?.clone());
1374 }
1375 ValueWord::from_simulation_call(name.clone(), out)
1376 }
1377 })
1378}
1379
1380fn enum_to_snapshot(value: &EnumValue, store: &SnapshotStore) -> Result<EnumValueSnapshot> {
1381 Ok(EnumValueSnapshot {
1382 enum_name: value.enum_name.clone(),
1383 variant: value.variant.clone(),
1384 payload: match &value.payload {
1385 EnumPayload::Unit => EnumPayloadSnapshot::Unit,
1386 EnumPayload::Tuple(values) => {
1387 let mut out = Vec::new();
1388 for v in values.iter() {
1389 out.push(nanboxed_to_serializable(v, store)?);
1390 }
1391 EnumPayloadSnapshot::Tuple(out)
1392 }
1393 EnumPayload::Struct(map) => {
1394 let mut out = Vec::new();
1395 for (k, v) in map.iter() {
1396 out.push((k.clone(), nanboxed_to_serializable(v, store)?));
1397 }
1398 EnumPayloadSnapshot::Struct(out)
1399 }
1400 },
1401 })
1402}
1403
1404fn snapshot_to_enum(snapshot: &EnumValueSnapshot, store: &SnapshotStore) -> Result<ValueWord> {
1405 Ok(ValueWord::from_enum(EnumValue {
1406 enum_name: snapshot.enum_name.clone(),
1407 variant: snapshot.variant.clone(),
1408 payload: match &snapshot.payload {
1409 EnumPayloadSnapshot::Unit => EnumPayload::Unit,
1410 EnumPayloadSnapshot::Tuple(values) => {
1411 let mut out = Vec::new();
1412 for v in values.iter() {
1413 out.push(serializable_to_nanboxed(v, store)?);
1414 }
1415 EnumPayload::Tuple(out)
1416 }
1417 EnumPayloadSnapshot::Struct(map) => {
1418 let mut out = HashMap::new();
1419 for (k, v) in map.iter() {
1420 out.insert(k.clone(), serializable_to_nanboxed(v, store)?);
1421 }
1422 EnumPayload::Struct(out)
1423 }
1424 },
1425 }))
1426}
1427
1428fn print_result_to_snapshot(
1429 result: &PrintResult,
1430 store: &SnapshotStore,
1431) -> Result<PrintableSnapshot> {
1432 let mut spans = Vec::new();
1433 for span in result.spans.iter() {
1434 match span {
1435 PrintSpan::Literal {
1436 text,
1437 start,
1438 end,
1439 span_id,
1440 } => spans.push(PrintSpanSnapshot::Literal {
1441 text: text.clone(),
1442 start: *start,
1443 end: *end,
1444 span_id: span_id.clone(),
1445 }),
1446 PrintSpan::Value {
1447 text,
1448 start,
1449 end,
1450 span_id,
1451 variable_name,
1452 raw_value,
1453 type_name,
1454 current_format,
1455 format_params,
1456 } => {
1457 let mut params = HashMap::new();
1458 for (k, v) in format_params.iter() {
1459 params.insert(k.clone(), nanboxed_to_serializable(&v.clone(), store)?);
1460 }
1461 spans.push(PrintSpanSnapshot::Value {
1462 text: text.clone(),
1463 start: *start,
1464 end: *end,
1465 span_id: span_id.clone(),
1466 variable_name: variable_name.clone(),
1467 raw_value: Box::new(nanboxed_to_serializable(raw_value.as_ref(), store)?),
1468 type_name: type_name.clone(),
1469 current_format: current_format.clone(),
1470 format_params: params,
1471 });
1472 }
1473 }
1474 }
1475 Ok(PrintableSnapshot {
1476 rendered: result.rendered.clone(),
1477 spans,
1478 })
1479}
1480
1481fn snapshot_to_print_result(
1482 snapshot: &PrintableSnapshot,
1483 store: &SnapshotStore,
1484) -> Result<PrintResult> {
1485 let mut spans = Vec::new();
1486 for span in snapshot.spans.iter() {
1487 match span {
1488 PrintSpanSnapshot::Literal {
1489 text,
1490 start,
1491 end,
1492 span_id,
1493 } => {
1494 spans.push(PrintSpan::Literal {
1495 text: text.clone(),
1496 start: *start,
1497 end: *end,
1498 span_id: span_id.clone(),
1499 });
1500 }
1501 PrintSpanSnapshot::Value {
1502 text,
1503 start,
1504 end,
1505 span_id,
1506 variable_name,
1507 raw_value,
1508 type_name,
1509 current_format,
1510 format_params,
1511 } => {
1512 let mut params = HashMap::new();
1513 for (k, v) in format_params.iter() {
1514 params.insert(k.clone(), serializable_to_nanboxed(v, store)?.clone());
1515 }
1516 spans.push(PrintSpan::Value {
1517 text: text.clone(),
1518 start: *start,
1519 end: *end,
1520 span_id: span_id.clone(),
1521 variable_name: variable_name.clone(),
1522 raw_value: Box::new(serializable_to_nanboxed(raw_value, store)?.clone()),
1523 type_name: type_name.clone(),
1524 current_format: current_format.clone(),
1525 format_params: params,
1526 });
1527 }
1528 }
1529 }
1530 Ok(PrintResult {
1531 rendered: snapshot.rendered.clone(),
1532 spans,
1533 })
1534}
1535
1536pub(crate) fn serialize_dataframe(
1537 df: &DataFrame,
1538 store: &SnapshotStore,
1539) -> Result<SerializableDataFrame> {
1540 let mut columns: Vec<_> = df.columns.iter().collect();
1541 columns.sort_by(|a, b| a.0.cmp(b.0));
1542 let mut serialized_cols = Vec::with_capacity(columns.len());
1543 for (name, values) in columns.into_iter() {
1544 let blob = store_chunked_vec(values, DEFAULT_CHUNK_LEN, store)?;
1545 serialized_cols.push(SerializableDataFrameColumn {
1546 name: name.clone(),
1547 values: blob,
1548 });
1549 }
1550 Ok(SerializableDataFrame {
1551 id: df.id.clone(),
1552 timeframe: df.timeframe,
1553 timestamps: store_chunked_vec(&df.timestamps, DEFAULT_CHUNK_LEN, store)?,
1554 columns: serialized_cols,
1555 })
1556}
1557
1558pub(crate) fn deserialize_dataframe(
1559 serialized: SerializableDataFrame,
1560 store: &SnapshotStore,
1561) -> Result<DataFrame> {
1562 let timestamps: Vec<i64> = load_chunked_vec(&serialized.timestamps, store)?;
1563 let mut columns = HashMap::new();
1564 for col in serialized.columns.into_iter() {
1565 let values: Vec<f64> = load_chunked_vec(&col.values, store)?;
1566 columns.insert(col.name, values);
1567 }
1568 Ok(DataFrame {
1569 id: serialized.id,
1570 timeframe: serialized.timeframe,
1571 timestamps,
1572 columns,
1573 })
1574}
1575
1576fn serialize_datatable(dt: &DataTable, store: &SnapshotStore) -> Result<SerializableDataTable> {
1577 let mut buf = Vec::new();
1578 let schema = dt.inner().schema();
1579 let mut writer = arrow_ipc::writer::FileWriter::try_new(&mut buf, schema.as_ref())?;
1580 writer.write(dt.inner())?;
1581 writer.finish()?;
1582 let ipc_chunks = store_chunked_vec(&buf, BYTE_CHUNK_LEN, store)?;
1583 Ok(SerializableDataTable {
1584 ipc_chunks,
1585 type_name: dt.type_name().map(|s| s.to_string()),
1586 schema_id: dt.schema_id(),
1587 })
1588}
1589
1590fn deserialize_datatable(
1591 serialized: SerializableDataTable,
1592 store: &SnapshotStore,
1593) -> Result<DataTable> {
1594 let bytes = load_chunked_vec(&serialized.ipc_chunks, store)?;
1595 let cursor = std::io::Cursor::new(bytes);
1596 let mut reader = arrow_ipc::reader::FileReader::try_new(cursor, None)?;
1597 let batch = reader
1598 .next()
1599 .transpose()?
1600 .context("no RecordBatch in DataTable snapshot")?;
1601 let mut dt = DataTable::new(batch);
1602 if let Some(name) = serialized.type_name {
1603 dt = DataTable::with_type_name(dt.into_inner(), name);
1604 }
1605 if let Some(schema_id) = serialized.schema_id {
1606 dt = dt.with_schema_id(schema_id);
1607 }
1608 Ok(dt)
1609}
1610
1611#[cfg(test)]
1612mod tests {
1613 use super::*;
1614 use arrow_array::{Float64Array, Int64Array, RecordBatch};
1615 use arrow_schema::{DataType, Field, Schema};
1616 use std::sync::Arc;
1617
1618 fn make_test_table(ids: &[i64], values: &[f64]) -> DataTable {
1620 let schema = Arc::new(Schema::new(vec![
1621 Field::new("id", DataType::Int64, false),
1622 Field::new("value", DataType::Float64, false),
1623 ]));
1624 let batch = RecordBatch::try_new(
1625 schema,
1626 vec![
1627 Arc::new(Int64Array::from(ids.to_vec())),
1628 Arc::new(Float64Array::from(values.to_vec())),
1629 ],
1630 )
1631 .unwrap();
1632 DataTable::new(batch)
1633 }
1634
1635 #[test]
1636 fn test_datatable_snapshot_roundtrip_preserves_original_data() {
1637 let dir = tempfile::tempdir().unwrap();
1638 let store_path = dir.path().join("store");
1639
1640 let original_dt = make_test_table(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], &[150.0; 10]);
1641 assert_eq!(original_dt.row_count(), 10);
1642 let original_nb = ValueWord::from_datatable(Arc::new(original_dt));
1643
1644 let store = SnapshotStore::new(&store_path).unwrap();
1646 let serialized = nanboxed_to_serializable(&original_nb, &store).unwrap();
1647
1648 let restored_nb = serializable_to_nanboxed(&serialized, &store).unwrap();
1650 let restored = restored_nb.clone();
1651 let dt = restored
1652 .as_datatable()
1653 .expect("Expected DataTable after restore");
1654 assert_eq!(
1655 dt.row_count(),
1656 10,
1657 "restored table should have original 10 rows"
1658 );
1659 }
1660
1661 #[test]
1662 fn test_indexed_table_snapshot_roundtrip() {
1663 let dir = tempfile::tempdir().unwrap();
1664 let store = SnapshotStore::new(dir.path().join("store")).unwrap();
1665
1666 let dt = make_test_table(&[1, 2, 3], &[10.0, 20.0, 30.0]);
1667 let original_nb = ValueWord::from_indexed_table(1, Arc::new(dt), 0);
1668
1669 let serialized = nanboxed_to_serializable(&original_nb, &store).unwrap();
1670
1671 match &serialized {
1673 SerializableVMValue::IndexedTable {
1674 schema_id,
1675 index_col,
1676 ..
1677 } => {
1678 assert_eq!(schema_id, &1);
1679 assert_eq!(index_col, &0);
1680 }
1681 other => panic!(
1682 "Expected SerializableVMValue::IndexedTable, got {:?}",
1683 std::mem::discriminant(other)
1684 ),
1685 }
1686
1687 let restored = serializable_to_nanboxed(&serialized, &store)
1688 .unwrap()
1689 .clone();
1690 let (schema_id, table, index_col) = restored
1691 .as_indexed_table()
1692 .expect("Expected ValueWord::IndexedTable");
1693 assert_eq!(schema_id, 1);
1694 assert_eq!(index_col, 0);
1695 assert_eq!(table.row_count(), 3);
1696 }
1697
1698 #[test]
1699 fn test_typed_table_snapshot_roundtrip() {
1700 let dir = tempfile::tempdir().unwrap();
1701 let store = SnapshotStore::new(dir.path().join("store")).unwrap();
1702
1703 let dt = make_test_table(&[10, 20], &[1.5, 2.5]);
1704 let original_nb = ValueWord::from_typed_table(42, Arc::new(dt));
1705
1706 let serialized = nanboxed_to_serializable(&original_nb, &store).unwrap();
1707 let restored = serializable_to_nanboxed(&serialized, &store)
1708 .unwrap()
1709 .clone();
1710
1711 let (schema_id, table) = restored
1712 .as_typed_table()
1713 .expect("Expected ValueWord::TypedTable");
1714 assert_eq!(schema_id, 42);
1715 assert_eq!(table.row_count(), 2);
1716 let vals = table.get_f64_column("value").unwrap();
1717 assert!((vals.value(0) - 1.5).abs() < f64::EPSILON);
1718 assert!((vals.value(1) - 2.5).abs() < f64::EPSILON);
1719 }
1720
1721 #[test]
1724 fn test_float_array_typed_roundtrip() {
1725 let dir = tempfile::tempdir().unwrap();
1726 let store = SnapshotStore::new(dir.path().join("store")).unwrap();
1727
1728 let data: Vec<f64> = (0..1000).map(|i| i as f64 * 1.5).collect();
1729 let aligned = shape_value::AlignedVec::from_vec(data.clone());
1730 let buf = shape_value::AlignedTypedBuffer::from_aligned(aligned);
1731 let nb = ValueWord::from_float_array(Arc::new(buf));
1732
1733 let serialized = nanboxed_to_serializable(&nb, &store).unwrap();
1734 match &serialized {
1735 SerializableVMValue::TypedArray {
1736 element_kind, len, ..
1737 } => {
1738 assert_eq!(*element_kind, TypedArrayElementKind::F64);
1739 assert_eq!(*len, 1000);
1740 }
1741 other => panic!(
1742 "Expected TypedArray, got {:?}",
1743 std::mem::discriminant(other)
1744 ),
1745 }
1746
1747 let restored = serializable_to_nanboxed(&serialized, &store).unwrap();
1748 let hv = restored.as_heap_ref().unwrap();
1749 match hv {
1750 shape_value::heap_value::HeapValue::FloatArray(a) => {
1751 assert_eq!(a.len(), 1000);
1752 for i in 0..1000 {
1753 assert!((a.as_slice()[i] - data[i]).abs() < f64::EPSILON);
1754 }
1755 }
1756 _ => panic!("Expected FloatArray after restore"),
1757 }
1758 }
1759
1760 #[test]
1761 fn test_int_array_typed_roundtrip() {
1762 let dir = tempfile::tempdir().unwrap();
1763 let store = SnapshotStore::new(dir.path().join("store")).unwrap();
1764
1765 let data: Vec<i64> = (0..500).map(|i| i * 3 - 100).collect();
1766 let buf = shape_value::TypedBuffer::from_vec(data.clone());
1767 let nb = ValueWord::from_int_array(Arc::new(buf));
1768
1769 let serialized = nanboxed_to_serializable(&nb, &store).unwrap();
1770 match &serialized {
1771 SerializableVMValue::TypedArray {
1772 element_kind, len, ..
1773 } => {
1774 assert_eq!(*element_kind, TypedArrayElementKind::I64);
1775 assert_eq!(*len, 500);
1776 }
1777 other => panic!(
1778 "Expected TypedArray, got {:?}",
1779 std::mem::discriminant(other)
1780 ),
1781 }
1782
1783 let restored = serializable_to_nanboxed(&serialized, &store).unwrap();
1784 let hv = restored.as_heap_ref().unwrap();
1785 match hv {
1786 shape_value::heap_value::HeapValue::IntArray(a) => {
1787 assert_eq!(a.len(), 500);
1788 for i in 0..500 {
1789 assert_eq!(a.as_slice()[i], data[i]);
1790 }
1791 }
1792 _ => panic!("Expected IntArray after restore"),
1793 }
1794 }
1795
1796 #[test]
1797 fn test_matrix_roundtrip() {
1798 let dir = tempfile::tempdir().unwrap();
1799 let store = SnapshotStore::new(dir.path().join("store")).unwrap();
1800
1801 let data: Vec<f64> = (0..12).map(|i| i as f64).collect();
1802 let aligned = shape_value::AlignedVec::from_vec(data.clone());
1803 let matrix = shape_value::heap_value::MatrixData::from_flat(aligned, 3, 4);
1804 let nb = ValueWord::from_matrix(std::sync::Arc::new(matrix));
1805
1806 let serialized = nanboxed_to_serializable(&nb, &store).unwrap();
1807 match &serialized {
1808 SerializableVMValue::Matrix { rows, cols, .. } => {
1809 assert_eq!(*rows, 3);
1810 assert_eq!(*cols, 4);
1811 }
1812 other => panic!("Expected Matrix, got {:?}", std::mem::discriminant(other)),
1813 }
1814
1815 let restored = serializable_to_nanboxed(&serialized, &store).unwrap();
1816 let hv = restored.as_heap_ref().unwrap();
1817 match hv {
1818 shape_value::heap_value::HeapValue::Matrix(m) => {
1819 assert_eq!(m.rows, 3);
1820 assert_eq!(m.cols, 4);
1821 for i in 0..12 {
1822 assert!((m.data.as_slice()[i] - data[i]).abs() < f64::EPSILON);
1823 }
1824 }
1825 _ => panic!("Expected Matrix after restore"),
1826 }
1827 }
1828
1829 #[test]
1830 fn test_hashmap_typed_roundtrip() {
1831 let dir = tempfile::tempdir().unwrap();
1832 let store = SnapshotStore::new(dir.path().join("store")).unwrap();
1833
1834 let keys = vec![
1835 ValueWord::from_string(Arc::new("a".to_string())),
1836 ValueWord::from_string(Arc::new("b".to_string())),
1837 ];
1838 let values = vec![ValueWord::from_i64(1), ValueWord::from_i64(2)];
1839 let nb = ValueWord::from_hashmap_pairs(keys, values);
1840
1841 let serialized = nanboxed_to_serializable(&nb, &store).unwrap();
1842 match &serialized {
1843 SerializableVMValue::HashMap { keys, values } => {
1844 assert_eq!(keys.len(), 2);
1845 assert_eq!(values.len(), 2);
1846 }
1847 other => panic!("Expected HashMap, got {:?}", std::mem::discriminant(other)),
1848 }
1849
1850 let restored = serializable_to_nanboxed(&serialized, &store).unwrap();
1851 let hv = restored.as_heap_ref().unwrap();
1852 match hv {
1853 shape_value::heap_value::HeapValue::HashMap(d) => {
1854 assert_eq!(d.keys.len(), 2);
1855 assert_eq!(d.values.len(), 2);
1856 let key_a = ValueWord::from_string(Arc::new("a".to_string()));
1858 let idx = d.find_key(&key_a);
1859 assert!(idx.is_some(), "should find key 'a' in rebuilt index");
1860 }
1861 _ => panic!("Expected HashMap after restore"),
1862 }
1863 }
1864
1865 #[test]
1866 fn test_bool_array_typed_roundtrip() {
1867 let dir = tempfile::tempdir().unwrap();
1868 let store = SnapshotStore::new(dir.path().join("store")).unwrap();
1869
1870 let data: Vec<u8> = vec![1, 0, 1, 1, 0];
1871 let buf = shape_value::TypedBuffer::from_vec(data.clone());
1872 let nb = ValueWord::from_bool_array(Arc::new(buf));
1873
1874 let serialized = nanboxed_to_serializable(&nb, &store).unwrap();
1875 match &serialized {
1876 SerializableVMValue::TypedArray {
1877 element_kind, len, ..
1878 } => {
1879 assert_eq!(*element_kind, TypedArrayElementKind::Bool);
1880 assert_eq!(*len, 5);
1881 }
1882 other => panic!(
1883 "Expected TypedArray, got {:?}",
1884 std::mem::discriminant(other)
1885 ),
1886 }
1887
1888 let restored = serializable_to_nanboxed(&serialized, &store).unwrap();
1889 let hv = restored.as_heap_ref().unwrap();
1890 match hv {
1891 shape_value::heap_value::HeapValue::BoolArray(a) => {
1892 assert_eq!(a.len(), 5);
1893 assert_eq!(a.as_slice(), &data);
1894 }
1895 _ => panic!("Expected BoolArray after restore"),
1896 }
1897 }
1898
1899 #[test]
1902 fn test_all_snapshot_components_bincode_roundtrip() {
1903 use rust_decimal::Decimal;
1904 use shape_ast::ast::VarKind;
1905 use shape_ast::data::Timeframe;
1906
1907 let decimal_val = SerializableVMValue::Decimal(Decimal::new(31415, 4)); let bytes = bincode::serialize(&decimal_val).expect("serialize Decimal ValueWord");
1910 let decoded: SerializableVMValue =
1911 bincode::deserialize(&bytes).expect("deserialize Decimal ValueWord");
1912 match decoded {
1913 SerializableVMValue::Decimal(d) => assert_eq!(d, Decimal::new(31415, 4)),
1914 _ => panic!("wrong variant"),
1915 }
1916
1917 let vm_snap = VmSnapshot {
1919 ip: 42,
1920 stack: vec![
1921 SerializableVMValue::Int(1),
1922 SerializableVMValue::Decimal(Decimal::new(99999, 2)),
1923 SerializableVMValue::String("hello".into()),
1924 ],
1925 locals: vec![SerializableVMValue::Decimal(Decimal::new(0, 0))],
1926 module_bindings: vec![SerializableVMValue::Number(3.14)],
1927 call_stack: vec![],
1928 loop_stack: vec![],
1929 timeframe_stack: vec![],
1930 exception_handlers: vec![],
1931 ip_blob_hash: None,
1932 ip_local_offset: None,
1933 ip_function_id: None,
1934 };
1935 let bytes = bincode::serialize(&vm_snap).expect("serialize VmSnapshot");
1936 let decoded: VmSnapshot = bincode::deserialize(&bytes).expect("deserialize VmSnapshot");
1937 assert_eq!(decoded.ip, 42);
1938 assert_eq!(decoded.stack.len(), 3);
1939
1940 let ctx_snap = ContextSnapshot {
1942 data_load_mode: crate::context::DataLoadMode::Async,
1943 data_cache: None,
1944 current_id: Some("test".into()),
1945 current_row_index: 0,
1946 variable_scopes: vec![{
1947 let mut scope = HashMap::new();
1948 scope.insert(
1949 "price".into(),
1950 VariableSnapshot {
1951 value: SerializableVMValue::Decimal(Decimal::new(15099, 2)),
1952 kind: VarKind::Let,
1953 is_initialized: true,
1954 is_function_scoped: false,
1955 format_hint: None,
1956 format_overrides: None,
1957 },
1958 );
1959 scope
1960 }],
1961 reference_datetime: None,
1962 current_timeframe: Some(Timeframe::m1()),
1963 base_timeframe: None,
1964 date_range: None,
1965 range_start: 0,
1966 range_end: 0,
1967 range_active: false,
1968 type_alias_registry: HashMap::new(),
1969 enum_registry: HashMap::new(),
1970 struct_type_registry: HashMap::new(),
1971 suspension_state: None,
1972 };
1973 let bytes = bincode::serialize(&ctx_snap).expect("serialize ContextSnapshot");
1974 let decoded: ContextSnapshot =
1975 bincode::deserialize(&bytes).expect("deserialize ContextSnapshot");
1976 assert_eq!(decoded.variable_scopes.len(), 1);
1977
1978 let sem_snap = SemanticSnapshot {
1980 exported_symbols: HashSet::new(),
1981 };
1982 let bytes = bincode::serialize(&sem_snap).expect("serialize SemanticSnapshot");
1983 let _decoded: SemanticSnapshot =
1984 bincode::deserialize(&bytes).expect("deserialize SemanticSnapshot");
1985
1986 let exec_snap = ExecutionSnapshot {
1988 version: SNAPSHOT_VERSION,
1989 created_at_ms: 1234567890,
1990 semantic_hash: HashDigest::from_hex("abc123"),
1991 context_hash: HashDigest::from_hex("def456"),
1992 vm_hash: Some(HashDigest::from_hex("789aaa")),
1993 bytecode_hash: Some(HashDigest::from_hex("bbb000")),
1994 script_path: Some("/tmp/test.shape".into()),
1995 };
1996 let bytes = bincode::serialize(&exec_snap).expect("serialize ExecutionSnapshot");
1997 let decoded: ExecutionSnapshot =
1998 bincode::deserialize(&bytes).expect("deserialize ExecutionSnapshot");
1999 assert_eq!(decoded.version, SNAPSHOT_VERSION);
2000 }
2001}