1use super::*;
2
3impl UnifiedStore {
4 pub fn new() -> Self {
5 Self::with_config(UnifiedStoreConfig::default())
6 }
7
8 pub fn format_version(&self) -> u32 {
10 self.format_version.load(Ordering::SeqCst)
11 }
12
13 pub(crate) fn set_format_version(&self, version: u32) {
14 self.format_version.store(version, Ordering::SeqCst);
15 }
16
17 pub fn next_entity_id(&self) -> EntityId {
19 EntityId::new(self.next_entity_id.fetch_add(1, Ordering::SeqCst))
20 }
21
22 pub fn reserve_entity_ids(&self, n: u64) -> std::ops::Range<u64> {
25 let start = self.next_entity_id.fetch_add(n, Ordering::SeqCst);
26 start..start + n
27 }
28
29 pub(crate) fn register_entity_id(&self, id: EntityId) {
30 let candidate = id.raw().saturating_add(1);
31 let mut current = self.next_entity_id.load(Ordering::SeqCst);
32 while candidate > current {
33 match self.next_entity_id.compare_exchange(
34 current,
35 candidate,
36 Ordering::SeqCst,
37 Ordering::SeqCst,
38 ) {
39 Ok(_) => break,
40 Err(updated) => current = updated,
41 }
42 }
43 }
44
45 fn restore_snapshot_entity(
48 &self,
49 collection: &str,
50 mut entity: UnifiedEntity,
51 ) -> Result<EntityId, StoreError> {
52 let manager = self.get_or_create_collection(collection);
53 if entity.id.raw() == 0 {
54 entity.id = self.next_entity_id();
55 } else {
56 self.register_entity_id(entity.id);
57 }
58 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
59 if *row_id == 0 {
60 *row_id = manager.next_row_id();
61 } else {
62 manager.register_row_id(*row_id);
63 }
64 }
65 entity.ensure_table_logical_id();
66 self.context_index.index_entity(collection, &entity);
67 if self.config.auto_index_refs {
68 self.index_cross_refs(&entity, collection)?;
69 }
70 let graph_label = match &entity.kind {
71 EntityKind::GraphNode(node) => Some(node.label.clone()),
72 _ => None,
73 };
74 let id = manager.restore_snapshot_entity(entity)?;
75 if let Some(label) = graph_label {
76 self.update_graph_label_index(collection, &label, id);
77 }
78 Ok(id)
79 }
80
81 pub fn load_from_file(path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
93 let file = File::open(path)?;
94 let mut reader = BufReader::new(file);
95 let mut buf = Vec::new();
96 reader.read_to_end(&mut buf)?;
97 Self::load_from_bytes(&buf)
98 }
99
100 pub(crate) fn load_from_bytes(buf: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
101 Self::load_from_bytes_with_config(buf, UnifiedStoreConfig::default())
102 }
103
104 pub(crate) fn load_from_bytes_with_config(
105 buf: &[u8],
106 config: UnifiedStoreConfig,
107 ) -> Result<Self, Box<dyn std::error::Error>> {
108 let mut buf = buf.to_vec();
109 let version =
110 reddb_file::decode_native_store_header(&buf).map_err(|err| err.to_string())?;
111 let mut pos = 8;
112
113 reddb_file::verify_native_store_crc32_footer(&mut buf, version)
115 .map_err(|err| err.to_string())?;
116
117 let mut store = Self::with_config(config);
118 let embedded_wal_path = store.config.embedded_wal_path.take();
121 store.set_format_version(version);
122
123 let collection_count =
125 reddb_file::decode_native_dump_count(&buf, &mut pos).map_err(|e| e.to_string())?;
126
127 for _ in 0..collection_count {
129 let collection_header =
130 reddb_file::decode_native_dump_collection_header(&buf, &mut pos)
131 .map_err(|e| e.to_string())?;
132
133 store.get_or_create_collection(&collection_header.name);
137
138 for _ in 0..collection_header.entity_count {
140 if version >= STORE_VERSION_V7 {
141 let record_bytes = reddb_file::decode_native_dump_entity_record(&buf, &mut pos)
143 .map_err(|e| e.to_string())?;
144
145 let (entity, metadata) = Self::deserialize_entity_record(record_bytes, version)
146 .map_err(|e| format!("Entity record deserialization error: {e}"))?;
147
148 store.restore_snapshot_entity(&collection_header.name, entity.clone())?;
149
150 if let Some(metadata) = metadata {
151 if let Some(manager) = store.get_collection(&collection_header.name) {
152 let _ = manager.set_metadata(entity.id, metadata);
153 }
154 }
155 } else {
156 let entity = Self::read_entity_binary(&buf, &mut pos, version)?;
158 store.restore_snapshot_entity(&collection_header.name, entity)?;
159 }
160 }
161 }
162
163 if pos < buf.len() {
164 let cross_ref_count =
166 reddb_file::decode_native_dump_count(&buf, &mut pos).map_err(|e| e.to_string())?;
167
168 for _ in 0..cross_ref_count {
169 let cross_ref = reddb_file::decode_native_dump_cross_ref(&buf, &mut pos)
170 .map_err(|e| e.to_string())?;
171 let ref_type = RefType::from_byte(cross_ref.ref_type);
172
173 let source_collection = store
174 .get_any(EntityId::new(cross_ref.source_id))
175 .map(|(name, _)| name)
176 .unwrap_or_else(|| cross_ref.target_collection.clone());
177 let _ = store.add_cross_ref(
178 &source_collection,
179 EntityId::new(cross_ref.source_id),
180 &cross_ref.target_collection,
181 EntityId::new(cross_ref.target_id),
182 ref_type,
183 1.0,
184 );
185 }
186 }
187
188 if version >= reddb_file::STORE_VERSION_V10 && pos < buf.len() {
193 let aux = reddb_file::decode_native_dump_entity_record(&buf, &mut pos)
194 .map_err(|e| e.to_string())?;
195 if !aux.is_empty() {
196 store.set_aux_metadata(aux.to_vec());
197 }
198 }
199
200 if store.format_version() < STORE_VERSION_CURRENT {
201 store.set_format_version(STORE_VERSION_CURRENT);
202 }
203 store.config.embedded_wal_path = embedded_wal_path;
204
205 Ok(store)
206 }
207
208 pub fn save_to_file(&self, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
213 let buf = self.to_binary_dump_bytes();
214 reddb_file::write_native_store_bytes_atomically(path, &buf)?;
215 Ok(())
216 }
217
218 pub(crate) fn to_binary_dump_bytes(&self) -> Vec<u8> {
219 let _vault_guard = self.vault_publication_lock.lock();
220 let pending_vault_versions = self.pending_vault_versions.read();
221 let (_, excluded) = self.snapshot_manager.get().map_or_else(
222 || (0, Default::default()),
223 |manager| manager.checkpoint_excluded_state(),
224 );
225 let mut buf = Vec::new();
226
227 buf.extend_from_slice(&reddb_file::encode_native_store_header(
230 STORE_VERSION_CURRENT,
231 ));
232
233 let collections = self.collections.read();
235 reddb_file::encode_native_dump_count(&mut buf, collections.len() as u32);
236
237 let fv = STORE_VERSION_CURRENT;
238 for (name, manager) in collections.iter() {
239 let entities = manager.query_all(|entity| {
241 if pending_vault_versions.contains(&entity.id) {
242 return false;
243 }
244 !matches!(entity.data, EntityData::Row(_) | EntityData::Vector(_))
245 || !excluded.contains(&entity.xmin)
246 });
247 reddb_file::encode_native_dump_collection_header(&mut buf, name, entities.len() as u32);
248
249 for mut entity in entities {
252 if matches!(entity.data, EntityData::Row(_)) && excluded.contains(&entity.xmax) {
255 entity.xmax = 0;
256 }
257 let metadata = manager.get_metadata(entity.id);
258 let record = Self::serialize_entity_record(&entity, metadata.as_ref(), fv);
259 reddb_file::encode_native_dump_entity_record(&mut buf, &record);
260 }
261 }
262
263 let cross_refs = self.cross_refs.read();
265 let total_refs: usize = cross_refs.values().map(|v| v.len()).sum();
266 reddb_file::encode_native_dump_count(&mut buf, total_refs as u32);
267
268 for (source_id, refs) in cross_refs.iter() {
269 for (target_id, ref_type, collection) in refs {
270 reddb_file::encode_native_dump_cross_ref(
271 &mut buf,
272 source_id.raw(),
273 target_id.raw(),
274 ref_type.to_byte(),
275 collection,
276 );
277 }
278 }
279
280 {
284 let aux = self.aux_metadata.read();
285 reddb_file::encode_native_dump_entity_record(&mut buf, &aux);
286 }
287
288 self.set_format_version(STORE_VERSION_CURRENT);
289
290 reddb_file::append_native_store_crc32_footer(&mut buf);
291 buf
292 }
293
294 pub(crate) fn read_entity_binary(
296 buf: &[u8],
297 pos: &mut usize,
298 format_version: u32,
299 ) -> Result<UnifiedEntity, Box<dyn std::error::Error>> {
300 let id = read_varu64(buf, pos).map_err(|e| format!("Failed to read entity id: {:?}", e))?;
302
303 let kind_type = buf[*pos];
305 *pos += 1;
306
307 let kind = match kind_type {
309 0 => {
310 let table_len = Self::read_varu32_safe(buf, pos)?;
312 let table = String::from_utf8(buf[*pos..*pos + table_len].to_vec())?;
313 *pos += table_len;
314 let row_id = Self::read_varu64_safe(buf, pos)?;
315 EntityKind::TableRow {
316 table: table.into(),
317 row_id,
318 }
319 }
320 1 => {
321 let label_len = Self::read_varu32_safe(buf, pos)?;
323 let label = String::from_utf8(buf[*pos..*pos + label_len].to_vec())?;
324 *pos += label_len;
325 let node_type_len = Self::read_varu32_safe(buf, pos)?;
326 let node_type = String::from_utf8(buf[*pos..*pos + node_type_len].to_vec())?;
327 *pos += node_type_len;
328 EntityKind::GraphNode(Box::new(GraphNodeKind { label, node_type }))
329 }
330 2 => {
331 let label_len = Self::read_varu32_safe(buf, pos)?;
333 let label = String::from_utf8(buf[*pos..*pos + label_len].to_vec())?;
334 *pos += label_len;
335 let from_node_len = Self::read_varu32_safe(buf, pos)?;
336 let from_node = String::from_utf8(buf[*pos..*pos + from_node_len].to_vec())?;
337 *pos += from_node_len;
338 let to_node_len = Self::read_varu32_safe(buf, pos)?;
339 let to_node = String::from_utf8(buf[*pos..*pos + to_node_len].to_vec())?;
340 *pos += to_node_len;
341 let weight =
342 u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
343 *pos += 4;
344 EntityKind::GraphEdge(Box::new(GraphEdgeKind {
345 label,
346 from_node,
347 to_node,
348 weight,
349 }))
350 }
351 3 => {
352 let collection_len = Self::read_varu32_safe(buf, pos)?;
354 let collection = String::from_utf8(buf[*pos..*pos + collection_len].to_vec())?;
355 *pos += collection_len;
356 EntityKind::Vector { collection }
357 }
358 4 => {
359 let series_len = Self::read_varu32_safe(buf, pos)?;
361 let series = String::from_utf8(buf[*pos..*pos + series_len].to_vec())?;
362 *pos += series_len;
363 let metric_len = Self::read_varu32_safe(buf, pos)?;
364 let metric = String::from_utf8(buf[*pos..*pos + metric_len].to_vec())?;
365 *pos += metric_len;
366 EntityKind::TimeSeriesPoint(Box::new(TimeSeriesPointKind { series, metric }))
367 }
368 5 => {
369 let queue_len = Self::read_varu32_safe(buf, pos)?;
371 let queue = String::from_utf8(buf[*pos..*pos + queue_len].to_vec())?;
372 *pos += queue_len;
373 let position = Self::read_varu64_safe(buf, pos)?;
374 EntityKind::QueueMessage { queue, position }
375 }
376 _ => return Err(format!("Unknown EntityKind type: {}", kind_type).into()),
377 };
378
379 let data_type = buf[*pos];
381 *pos += 1;
382
383 let mut data = match data_type {
385 0 => {
386 let col_count = Self::read_varu32_safe(buf, pos)?;
388 let mut columns = Vec::with_capacity(col_count);
389 for _ in 0..col_count {
390 columns.push(Self::read_value_binary(buf, pos)?);
391 }
392 EntityData::Row(RowData::new(columns))
393 }
394 1 => {
395 let prop_count = Self::read_varu32_safe(buf, pos)?;
397 let mut properties = HashMap::new();
398 for _ in 0..prop_count {
399 let key_len = Self::read_varu32_safe(buf, pos)?;
400 let key = String::from_utf8(buf[*pos..*pos + key_len].to_vec())?;
401 *pos += key_len;
402 let value = Self::read_value_binary(buf, pos)?;
403 properties.insert(key, value);
404 }
405 EntityData::Node(NodeData::with_properties(properties))
406 }
407 2 => {
408 let weight_bytes = [buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]];
410 *pos += 4;
411 let weight = f32::from_le_bytes(weight_bytes);
412 let prop_count = Self::read_varu32_safe(buf, pos)?;
413 let mut properties = HashMap::new();
414 for _ in 0..prop_count {
415 let key_len = Self::read_varu32_safe(buf, pos)?;
416 let key = String::from_utf8(buf[*pos..*pos + key_len].to_vec())?;
417 *pos += key_len;
418 let value = Self::read_value_binary(buf, pos)?;
419 properties.insert(key, value);
420 }
421 let mut edge = EdgeData::new(weight);
422 edge.properties = properties;
423 EntityData::Edge(edge)
424 }
425 3 => {
426 let dim = Self::read_varu32_safe(buf, pos)?;
428 let mut dense = Vec::with_capacity(dim);
429 for _ in 0..dim {
430 let bytes = [buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]];
431 *pos += 4;
432 dense.push(f32::from_le_bytes(bytes));
433 }
434 let mut vector = VectorData::new(dense);
435 if format_version >= STORE_VERSION_V6 {
436 let has_content = buf[*pos] != 0;
437 *pos += 1;
438 if has_content {
439 let content_len = Self::read_varu32_safe(buf, pos)?;
440 vector.content =
441 Some(String::from_utf8(buf[*pos..*pos + content_len].to_vec())?);
442 *pos += content_len;
443 }
444 }
445 EntityData::Vector(vector)
446 }
447 4 => {
448 let metric_len = Self::read_varu32_safe(buf, pos)?;
450 let metric = String::from_utf8(buf[*pos..*pos + metric_len].to_vec())?;
451 *pos += metric_len;
452 let timestamp_ns = Self::read_varu64_safe(buf, pos)?;
453 let value_bytes = [
454 buf[*pos],
455 buf[*pos + 1],
456 buf[*pos + 2],
457 buf[*pos + 3],
458 buf[*pos + 4],
459 buf[*pos + 5],
460 buf[*pos + 6],
461 buf[*pos + 7],
462 ];
463 *pos += 8;
464 let mut tags = HashMap::new();
465 let series_id = if format_version >= STORE_VERSION_V11 {
466 let interned = Self::read_varu32_safe(buf, pos)?;
471 if interned == 1 {
472 Some(Self::read_varu64_safe(buf, pos)?)
473 } else {
474 let tag_count = Self::read_varu32_safe(buf, pos)?;
475 tags = HashMap::with_capacity(tag_count);
476 for _ in 0..tag_count {
477 let key_len = Self::read_varu32_safe(buf, pos)?;
478 let key = String::from_utf8(buf[*pos..*pos + key_len].to_vec())?;
479 *pos += key_len;
480 let value_len = Self::read_varu32_safe(buf, pos)?;
481 let value = String::from_utf8(buf[*pos..*pos + value_len].to_vec())?;
482 *pos += value_len;
483 tags.insert(key, value);
484 }
485 None
486 }
487 } else if format_version >= STORE_VERSION_V5 {
488 let tag_count = Self::read_varu32_safe(buf, pos)?;
489 tags = HashMap::with_capacity(tag_count);
490 for _ in 0..tag_count {
491 let key_len = Self::read_varu32_safe(buf, pos)?;
492 let key = String::from_utf8(buf[*pos..*pos + key_len].to_vec())?;
493 *pos += key_len;
494 let value_len = Self::read_varu32_safe(buf, pos)?;
495 let value = String::from_utf8(buf[*pos..*pos + value_len].to_vec())?;
496 *pos += value_len;
497 tags.insert(key, value);
498 }
499 None
500 } else {
501 None
502 };
503 let fields = if format_version >= STORE_VERSION_V12 {
504 let field_count = Self::read_varu32_safe(buf, pos)?;
505 let mut fields = HashMap::with_capacity(field_count);
506 for _ in 0..field_count {
507 let key_len = Self::read_varu32_safe(buf, pos)?;
508 let key = String::from_utf8(buf[*pos..*pos + key_len].to_vec())?;
509 *pos += key_len;
510 let value = Self::read_value_binary(buf, pos)?;
511 fields.insert(key, value);
512 }
513 fields
514 } else {
515 HashMap::new()
516 };
517 EntityData::TimeSeries(crate::storage::unified::entity::TimeSeriesData {
518 metric,
519 series_id,
520 timestamp_ns,
521 value: f64::from_le_bytes(value_bytes),
522 tags,
523 fields,
524 })
525 }
526 5 => {
527 let payload = Self::read_value_binary(buf, pos)?;
529 let enqueued_at_ns = Self::read_varu64_safe(buf, pos)?;
530 let attempts = Self::read_varu32_safe(buf, pos)? as u32;
531 EntityData::QueueMessage(crate::storage::unified::entity::QueueMessageData {
532 payload,
533 priority: None,
534 enqueued_at_ns,
535 attempts,
536 max_attempts: 3,
537 acked: false,
538 })
539 }
540 6 => {
541 let field_count = Self::read_varu32_safe(buf, pos)?;
543 let mut named = HashMap::with_capacity(field_count);
544 for _ in 0..field_count {
545 let key_len = Self::read_varu32_safe(buf, pos)?;
546 let key = String::from_utf8(buf[*pos..*pos + key_len].to_vec())?;
547 *pos += key_len;
548 let value = Self::read_value_binary(buf, pos)?;
549 named.insert(key, value);
550 }
551 EntityData::Row(RowData {
552 columns: Vec::new(),
553 named: Some(named),
554 schema: None,
555 })
556 }
557 _ => return Err(format!("Unknown EntityData type: {}", data_type).into()),
558 };
559
560 let created_at = Self::read_varu64_safe(buf, pos)?;
562 let updated_at = Self::read_varu64_safe(buf, pos)?;
563
564 let embedding_count = Self::read_varu32_safe(buf, pos)?;
566 let mut embeddings = Vec::with_capacity(embedding_count);
567 for _ in 0..embedding_count {
568 let name_len = Self::read_varu32_safe(buf, pos)?;
569 let name = String::from_utf8(buf[*pos..*pos + name_len].to_vec())?;
570 *pos += name_len;
571
572 let dim = Self::read_varu32_safe(buf, pos)?;
573 let mut vector = Vec::with_capacity(dim);
574 for _ in 0..dim {
575 let bytes = [buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]];
576 *pos += 4;
577 vector.push(f32::from_le_bytes(bytes));
578 }
579
580 let model_len = Self::read_varu32_safe(buf, pos)?;
581 let model = String::from_utf8(buf[*pos..*pos + model_len].to_vec())?;
582 *pos += model_len;
583
584 embeddings.push(EmbeddingSlot::new(name, vector, model));
585 }
586
587 let cross_ref_count = Self::read_varu32_safe(buf, pos)?;
589 let mut cross_refs = Vec::with_capacity(cross_ref_count);
590 for _ in 0..cross_ref_count {
591 let source = Self::read_varu64_safe(buf, pos)?;
592 let target = Self::read_varu64_safe(buf, pos)?;
593 let ref_type_byte = buf[*pos];
594 *pos += 1;
595 let (target_collection, weight, created_at) = if format_version >= STORE_VERSION_V2 {
596 let coll_len = Self::read_varu32_safe(buf, pos)?;
597 let collection = String::from_utf8(buf[*pos..*pos + coll_len].to_vec())?;
598 *pos += coll_len;
599 let weight_bytes = [buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]];
600 *pos += 4;
601 let weight = f32::from_le_bytes(weight_bytes);
602 let created_at = Self::read_varu64_safe(buf, pos)?;
603 (collection, weight, created_at)
604 } else {
605 (String::new(), 1.0, 0)
606 };
607
608 let mut cross_ref = CrossRef::new(
609 EntityId::new(source),
610 EntityId::new(target),
611 target_collection,
612 RefType::from_byte(ref_type_byte),
613 );
614 cross_ref.weight = weight;
615 cross_ref.created_at = created_at;
616 cross_refs.push(cross_ref);
617 }
618
619 let sequence_id = Self::read_varu64_safe(buf, pos)?;
621
622 if format_version >= STORE_VERSION_V4 {
623 if let EntityData::QueueMessage(message) = &mut data {
624 if *pos < buf.len() {
625 let priority_present = buf[*pos] != 0;
626 *pos += 1;
627 message.priority = if priority_present {
628 if *pos + 4 > buf.len() {
629 return Err("truncated queue priority".into());
630 }
631 let bytes = [buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]];
632 *pos += 4;
633 Some(i32::from_le_bytes(bytes))
634 } else {
635 None
636 };
637 message.max_attempts = Self::read_varu32_safe(buf, pos)? as u32;
638 if *pos >= buf.len() {
639 return Err("truncated queue ack flag".into());
640 }
641 message.acked = buf[*pos] != 0;
642 *pos += 1;
643 }
644 }
645 }
646
647 let mut entity = UnifiedEntity::new(EntityId::new(id), kind, data);
648 entity.created_at = created_at;
649 entity.updated_at = updated_at;
650 entity.sequence_id = sequence_id;
651 if format_version >= STORE_VERSION_V8 && *pos < buf.len() {
652 let has_logical_id = buf[*pos] != 0;
653 *pos += 1;
654 if has_logical_id {
655 let logical_id = Self::read_varu64_safe(buf, pos)?;
656 entity.set_logical_id(EntityId::new(logical_id));
657 }
658 }
659 if format_version >= STORE_VERSION_V9 && *pos < buf.len() {
660 let xmin = Self::read_varu64_safe(buf, pos)?;
661 let xmax = Self::read_varu64_safe(buf, pos)?;
662 entity.set_xmin(xmin);
663 entity.set_xmax(xmax);
664 }
665 if !embeddings.is_empty() || !cross_refs.is_empty() {
666 entity.embeddings_mut().extend(embeddings);
667 entity.cross_refs_mut().extend(cross_refs);
668 }
669
670 Ok(entity)
671 }
672
673 fn read_varu32_safe(buf: &[u8], pos: &mut usize) -> Result<usize, Box<dyn std::error::Error>> {
675 read_varu32(buf, pos)
676 .map(|v| v as usize)
677 .map_err(|e| format!("Decode error: {:?}", e).into())
678 }
679
680 fn read_varu64_safe(buf: &[u8], pos: &mut usize) -> Result<u64, Box<dyn std::error::Error>> {
682 read_varu64(buf, pos).map_err(|e| format!("Decode error: {:?}", e).into())
683 }
684
685 pub(crate) fn write_entity_binary(
687 buf: &mut Vec<u8>,
688 entity: &UnifiedEntity,
689 format_version: u32,
690 ) {
691 write_varu64(buf, entity.id.raw());
693
694 match &entity.kind {
696 EntityKind::TableRow { table, row_id } => {
697 buf.push(0);
698 write_varu32(buf, table.len() as u32);
699 buf.extend_from_slice(table.as_bytes());
700 write_varu64(buf, *row_id);
701 }
702 EntityKind::GraphNode(ref node) => {
703 buf.push(1);
704 write_varu32(buf, node.label.len() as u32);
705 buf.extend_from_slice(node.label.as_bytes());
706 write_varu32(buf, node.node_type.len() as u32);
707 buf.extend_from_slice(node.node_type.as_bytes());
708 }
709 EntityKind::GraphEdge(ref edge) => {
710 buf.push(2);
711 write_varu32(buf, edge.label.len() as u32);
712 buf.extend_from_slice(edge.label.as_bytes());
713 write_varu32(buf, edge.from_node.len() as u32);
714 buf.extend_from_slice(edge.from_node.as_bytes());
715 write_varu32(buf, edge.to_node.len() as u32);
716 buf.extend_from_slice(edge.to_node.as_bytes());
717 buf.extend_from_slice(&edge.weight.to_le_bytes());
718 }
719 EntityKind::Vector { collection } => {
720 buf.push(3);
721 write_varu32(buf, collection.len() as u32);
722 buf.extend_from_slice(collection.as_bytes());
723 }
724 EntityKind::TimeSeriesPoint(ref ts) => {
725 buf.push(4);
726 write_varu32(buf, ts.series.len() as u32);
727 buf.extend_from_slice(ts.series.as_bytes());
728 write_varu32(buf, ts.metric.len() as u32);
729 buf.extend_from_slice(ts.metric.as_bytes());
730 }
731 EntityKind::QueueMessage { queue, position } => {
732 buf.push(5);
733 write_varu32(buf, queue.len() as u32);
734 buf.extend_from_slice(queue.as_bytes());
735 write_varu64(buf, *position);
736 }
737 }
738
739 match &entity.data {
741 EntityData::Row(row) => {
742 if let Some(ref named) = row.named {
743 buf.push(6);
750 write_varu32(buf, named.len() as u32);
751 let mut entries: Vec<_> = named.iter().collect();
752 entries.sort_by(|a, b| a.0.as_str().cmp(b.0.as_str()));
753 for (key, value) in entries {
754 write_varu32(buf, key.len() as u32);
755 buf.extend_from_slice(key.as_bytes());
756 Self::write_value_binary(buf, value);
757 }
758 } else {
759 buf.push(0);
760 write_varu32(buf, row.columns.len() as u32);
761 for col in &row.columns {
762 Self::write_value_binary(buf, col);
763 }
764 }
765 }
766 EntityData::Node(node) => {
767 buf.push(1);
768 write_varu32(buf, node.properties.len() as u32);
769 let mut entries: Vec<_> = node.properties.iter().collect();
770 entries.sort_by(|a, b| a.0.as_str().cmp(b.0.as_str()));
771 for (key, value) in entries {
772 write_varu32(buf, key.len() as u32);
773 buf.extend_from_slice(key.as_bytes());
774 Self::write_value_binary(buf, value);
775 }
776 }
777 EntityData::Edge(edge) => {
778 buf.push(2);
779 buf.extend_from_slice(&edge.weight.to_le_bytes());
780 write_varu32(buf, edge.properties.len() as u32);
781 let mut entries: Vec<_> = edge.properties.iter().collect();
782 entries.sort_by(|a, b| a.0.as_str().cmp(b.0.as_str()));
783 for (key, value) in entries {
784 write_varu32(buf, key.len() as u32);
785 buf.extend_from_slice(key.as_bytes());
786 Self::write_value_binary(buf, value);
787 }
788 }
789 EntityData::Vector(vec) => {
790 buf.push(3);
791 write_varu32(buf, vec.dense.len() as u32);
792 for f in &vec.dense {
793 buf.extend_from_slice(&f.to_le_bytes());
794 }
795 if format_version >= STORE_VERSION_V6 {
796 buf.push(u8::from(vec.content.is_some()));
797 if let Some(content) = &vec.content {
798 write_varu32(buf, content.len() as u32);
799 buf.extend_from_slice(content.as_bytes());
800 }
801 }
802 }
803 EntityData::TimeSeries(ts) => {
804 buf.push(4);
805 write_varu32(buf, ts.metric.len() as u32);
806 buf.extend_from_slice(ts.metric.as_bytes());
807 write_varu64(buf, ts.timestamp_ns);
808 buf.extend_from_slice(&ts.value.to_le_bytes());
809 if format_version >= STORE_VERSION_V11 {
810 match ts.series_id {
816 Some(series_id) => {
817 write_varu32(buf, 1);
818 write_varu64(buf, series_id);
819 }
820 None => {
821 write_varu32(buf, 0);
822 write_varu32(buf, ts.tags.len() as u32);
823 let mut tag_entries: Vec<_> = ts.tags.iter().collect();
824 tag_entries.sort_by(|a, b| a.0.as_str().cmp(b.0.as_str()));
825 for (key, value) in tag_entries {
826 write_varu32(buf, key.len() as u32);
827 buf.extend_from_slice(key.as_bytes());
828 write_varu32(buf, value.len() as u32);
829 buf.extend_from_slice(value.as_bytes());
830 }
831 }
832 }
833 } else if format_version >= STORE_VERSION_V5 {
834 write_varu32(buf, ts.tags.len() as u32);
835 let mut tag_entries: Vec<_> = ts.tags.iter().collect();
836 tag_entries.sort_by(|a, b| a.0.as_str().cmp(b.0.as_str()));
837 for (key, value) in tag_entries {
838 write_varu32(buf, key.len() as u32);
839 buf.extend_from_slice(key.as_bytes());
840 write_varu32(buf, value.len() as u32);
841 buf.extend_from_slice(value.as_bytes());
842 }
843 }
844 if format_version >= STORE_VERSION_V12 {
845 write_varu32(buf, ts.fields.len() as u32);
846 let mut field_entries: Vec<_> = ts.fields.iter().collect();
847 field_entries.sort_by(|a, b| a.0.as_str().cmp(b.0.as_str()));
848 for (key, value) in field_entries {
849 write_varu32(buf, key.len() as u32);
850 buf.extend_from_slice(key.as_bytes());
851 Self::write_value_binary(buf, value);
852 }
853 }
854 }
855 EntityData::QueueMessage(msg) => {
856 buf.push(5);
857 Self::write_value_binary(buf, &msg.payload);
858 write_varu64(buf, msg.enqueued_at_ns);
859 write_varu32(buf, msg.attempts);
860 }
861 }
862
863 write_varu64(buf, entity.created_at);
865 write_varu64(buf, entity.updated_at);
866
867 write_varu32(buf, entity.embeddings().len() as u32);
869 for emb in entity.embeddings() {
870 write_varu32(buf, emb.name.len() as u32);
871 buf.extend_from_slice(emb.name.as_bytes());
872 write_varu32(buf, emb.vector.len() as u32);
873 for f in &emb.vector {
874 buf.extend_from_slice(&f.to_le_bytes());
875 }
876 write_varu32(buf, emb.model.len() as u32);
877 buf.extend_from_slice(emb.model.as_bytes());
878 }
879
880 write_varu32(buf, entity.cross_refs().len() as u32);
882 for cross_ref in entity.cross_refs() {
883 write_varu64(buf, cross_ref.source.raw());
884 write_varu64(buf, cross_ref.target.raw());
885 buf.push(cross_ref.ref_type.to_byte());
886 if format_version >= STORE_VERSION_V2 {
887 write_varu32(buf, cross_ref.target_collection.len() as u32);
888 buf.extend_from_slice(cross_ref.target_collection.as_bytes());
889 buf.extend_from_slice(&cross_ref.weight.to_le_bytes());
890 write_varu64(buf, cross_ref.created_at);
891 }
892 }
893
894 write_varu64(buf, entity.sequence_id);
896
897 if format_version >= STORE_VERSION_V4 {
898 if let EntityData::QueueMessage(message) = &entity.data {
899 buf.push(u8::from(message.priority.is_some()));
900 if let Some(priority) = message.priority {
901 buf.extend_from_slice(&priority.to_le_bytes());
902 }
903 write_varu32(buf, message.max_attempts);
904 buf.push(u8::from(message.acked));
905 }
906 }
907
908 if format_version >= STORE_VERSION_V8 {
909 buf.push(u8::from(entity.has_explicit_logical_id()));
910 if entity.has_explicit_logical_id() {
911 write_varu64(buf, entity.logical_id().raw());
912 }
913 }
914 if format_version >= STORE_VERSION_V9 {
915 write_varu64(buf, entity.xmin);
916 write_varu64(buf, entity.xmax);
917 }
918 }
919
920 fn read_value_binary(buf: &[u8], pos: &mut usize) -> Result<Value, Box<dyn std::error::Error>> {
925 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
926
927 let type_byte = buf[*pos];
928 *pos += 1;
929
930 Ok(match type_byte {
931 0 => Value::Null,
932 1 => {
933 let b = buf[*pos] != 0;
934 *pos += 1;
935 Value::Boolean(b)
936 }
937 2 => {
938 let val = i64::from_le_bytes([
939 buf[*pos],
940 buf[*pos + 1],
941 buf[*pos + 2],
942 buf[*pos + 3],
943 buf[*pos + 4],
944 buf[*pos + 5],
945 buf[*pos + 6],
946 buf[*pos + 7],
947 ]);
948 *pos += 8;
949 Value::Integer(val)
950 }
951 3 => {
952 let val = u64::from_le_bytes([
953 buf[*pos],
954 buf[*pos + 1],
955 buf[*pos + 2],
956 buf[*pos + 3],
957 buf[*pos + 4],
958 buf[*pos + 5],
959 buf[*pos + 6],
960 buf[*pos + 7],
961 ]);
962 *pos += 8;
963 Value::UnsignedInteger(val)
964 }
965 4 => {
966 let val = f64::from_le_bytes([
967 buf[*pos],
968 buf[*pos + 1],
969 buf[*pos + 2],
970 buf[*pos + 3],
971 buf[*pos + 4],
972 buf[*pos + 5],
973 buf[*pos + 6],
974 buf[*pos + 7],
975 ]);
976 *pos += 8;
977 Value::Float(val)
978 }
979 5 => {
980 let len = Self::read_varu32_safe(buf, pos)?;
981 let s = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
982 *pos += len;
983 Value::text(s)
984 }
985 6 => {
986 let len = Self::read_varu32_safe(buf, pos)?;
987 let bytes = buf[*pos..*pos + len].to_vec();
988 *pos += len;
989 Value::Blob(bytes)
990 }
991 7 => {
992 let val = i64::from_le_bytes([
993 buf[*pos],
994 buf[*pos + 1],
995 buf[*pos + 2],
996 buf[*pos + 3],
997 buf[*pos + 4],
998 buf[*pos + 5],
999 buf[*pos + 6],
1000 buf[*pos + 7],
1001 ]);
1002 *pos += 8;
1003 Value::Timestamp(val)
1004 }
1005 8 => {
1006 let val = i64::from_le_bytes([
1007 buf[*pos],
1008 buf[*pos + 1],
1009 buf[*pos + 2],
1010 buf[*pos + 3],
1011 buf[*pos + 4],
1012 buf[*pos + 5],
1013 buf[*pos + 6],
1014 buf[*pos + 7],
1015 ]);
1016 *pos += 8;
1017 Value::Duration(val)
1018 }
1019 9 => {
1020 let version = buf[*pos];
1022 *pos += 1;
1023 if version == 4 {
1024 let octets = [buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]];
1025 *pos += 4;
1026 Value::IpAddr(IpAddr::V4(Ipv4Addr::from(octets)))
1027 } else {
1028 let mut octets = [0u8; 16];
1029 octets.copy_from_slice(&buf[*pos..*pos + 16]);
1030 *pos += 16;
1031 Value::IpAddr(IpAddr::V6(Ipv6Addr::from(octets)))
1032 }
1033 }
1034 10 => {
1035 let mut mac = [0u8; 6];
1036 mac.copy_from_slice(&buf[*pos..*pos + 6]);
1037 *pos += 6;
1038 Value::MacAddr(mac)
1039 }
1040 11 => {
1041 let len = Self::read_varu32_safe(buf, pos)?;
1042 let mut vector = Vec::with_capacity(len);
1043 for _ in 0..len {
1044 let bytes = [buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]];
1045 *pos += 4;
1046 vector.push(f32::from_le_bytes(bytes));
1047 }
1048 Value::Vector(vector)
1049 }
1050 12 => {
1051 let len = Self::read_varu32_safe(buf, pos)?;
1052 let bytes = buf[*pos..*pos + len].to_vec();
1053 *pos += len;
1054 Value::Json(bytes)
1055 }
1056 13 => {
1057 let mut uuid = [0u8; 16];
1058 uuid.copy_from_slice(&buf[*pos..*pos + 16]);
1059 *pos += 16;
1060 Value::Uuid(uuid)
1061 }
1062 14 => {
1063 let len = Self::read_varu32_safe(buf, pos)?;
1064 let s = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1065 *pos += len;
1066 Value::NodeRef(s)
1067 }
1068 15 => {
1069 let len = Self::read_varu32_safe(buf, pos)?;
1070 let s = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1071 *pos += len;
1072 Value::EdgeRef(s)
1073 }
1074 16 => {
1075 let len = Self::read_varu32_safe(buf, pos)?;
1076 let s = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1077 *pos += len;
1078 let id = u64::from_le_bytes([
1079 buf[*pos],
1080 buf[*pos + 1],
1081 buf[*pos + 2],
1082 buf[*pos + 3],
1083 buf[*pos + 4],
1084 buf[*pos + 5],
1085 buf[*pos + 6],
1086 buf[*pos + 7],
1087 ]);
1088 *pos += 8;
1089 Value::VectorRef(s, id)
1090 }
1091 17 => {
1092 let len = Self::read_varu32_safe(buf, pos)?;
1093 let s = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1094 *pos += len;
1095 let id = u64::from_le_bytes([
1096 buf[*pos],
1097 buf[*pos + 1],
1098 buf[*pos + 2],
1099 buf[*pos + 3],
1100 buf[*pos + 4],
1101 buf[*pos + 5],
1102 buf[*pos + 6],
1103 buf[*pos + 7],
1104 ]);
1105 *pos += 8;
1106 Value::RowRef(s, id)
1107 }
1108 18 => {
1109 let rgb = [buf[*pos], buf[*pos + 1], buf[*pos + 2]];
1110 *pos += 3;
1111 Value::Color(rgb)
1112 }
1113 19 => {
1114 let len = Self::read_varu32_safe(buf, pos)?;
1115 let s = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1116 *pos += len;
1117 Value::Email(s)
1118 }
1119 20 => {
1120 let len = Self::read_varu32_safe(buf, pos)?;
1121 let s = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1122 *pos += len;
1123 Value::Url(s)
1124 }
1125 21 => {
1126 let val = u64::from_le_bytes([
1127 buf[*pos],
1128 buf[*pos + 1],
1129 buf[*pos + 2],
1130 buf[*pos + 3],
1131 buf[*pos + 4],
1132 buf[*pos + 5],
1133 buf[*pos + 6],
1134 buf[*pos + 7],
1135 ]);
1136 *pos += 8;
1137 Value::Phone(val)
1138 }
1139 22 => {
1140 let val =
1141 u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1142 *pos += 4;
1143 Value::Semver(val)
1144 }
1145 23 => {
1146 let ip =
1147 u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1148 *pos += 4;
1149 let prefix = buf[*pos];
1150 *pos += 1;
1151 Value::Cidr(ip, prefix)
1152 }
1153 24 => {
1154 let val =
1155 i32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1156 *pos += 4;
1157 Value::Date(val)
1158 }
1159 25 => {
1160 let val =
1161 u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1162 *pos += 4;
1163 Value::Time(val)
1164 }
1165 26 => {
1166 let val = i64::from_le_bytes([
1167 buf[*pos],
1168 buf[*pos + 1],
1169 buf[*pos + 2],
1170 buf[*pos + 3],
1171 buf[*pos + 4],
1172 buf[*pos + 5],
1173 buf[*pos + 6],
1174 buf[*pos + 7],
1175 ]);
1176 *pos += 8;
1177 Value::Decimal(val)
1178 }
1179 27 => {
1180 let val = buf[*pos];
1181 *pos += 1;
1182 Value::EnumValue(val)
1183 }
1184 28 => {
1185 let len = Self::read_varu32_safe(buf, pos)?;
1186 let mut elems = Vec::with_capacity(len);
1187 for _ in 0..len {
1188 elems.push(Self::read_value_binary(buf, pos)?);
1189 }
1190 Value::Array(elems)
1191 }
1192 29 => {
1193 let val = i64::from_le_bytes([
1194 buf[*pos],
1195 buf[*pos + 1],
1196 buf[*pos + 2],
1197 buf[*pos + 3],
1198 buf[*pos + 4],
1199 buf[*pos + 5],
1200 buf[*pos + 6],
1201 buf[*pos + 7],
1202 ]);
1203 *pos += 8;
1204 Value::TimestampMs(val)
1205 }
1206 30 => {
1207 let val =
1208 u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1209 *pos += 4;
1210 Value::Ipv4(val)
1211 }
1212 31 => {
1213 let mut bytes = [0u8; 16];
1214 bytes.copy_from_slice(&buf[*pos..*pos + 16]);
1215 *pos += 16;
1216 Value::Ipv6(bytes)
1217 }
1218 32 => {
1219 let ip =
1220 u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1221 *pos += 4;
1222 let mask =
1223 u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1224 *pos += 4;
1225 Value::Subnet(ip, mask)
1226 }
1227 33 => {
1228 let val = u16::from_le_bytes([buf[*pos], buf[*pos + 1]]);
1229 *pos += 2;
1230 Value::Port(val)
1231 }
1232 34 => {
1233 let val =
1234 i32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1235 *pos += 4;
1236 Value::Latitude(val)
1237 }
1238 35 => {
1239 let val =
1240 i32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1241 *pos += 4;
1242 Value::Longitude(val)
1243 }
1244 36 => {
1245 let lat =
1246 i32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1247 *pos += 4;
1248 let lon =
1249 i32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1250 *pos += 4;
1251 Value::GeoPoint(lat, lon)
1252 }
1253 37 => {
1254 let c = [buf[*pos], buf[*pos + 1]];
1255 *pos += 2;
1256 Value::Country2(c)
1257 }
1258 38 => {
1259 let c = [buf[*pos], buf[*pos + 1], buf[*pos + 2]];
1260 *pos += 3;
1261 Value::Country3(c)
1262 }
1263 39 => {
1264 let c = [buf[*pos], buf[*pos + 1]];
1265 *pos += 2;
1266 Value::Lang2(c)
1267 }
1268 40 => {
1269 let c = [
1270 buf[*pos],
1271 buf[*pos + 1],
1272 buf[*pos + 2],
1273 buf[*pos + 3],
1274 buf[*pos + 4],
1275 ];
1276 *pos += 5;
1277 Value::Lang5(c)
1278 }
1279 41 => {
1280 let c = [buf[*pos], buf[*pos + 1], buf[*pos + 2]];
1281 *pos += 3;
1282 Value::Currency(c)
1283 }
1284 42 => {
1285 let rgba = [buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]];
1286 *pos += 4;
1287 Value::ColorAlpha(rgba)
1288 }
1289 43 => {
1290 let val = i64::from_le_bytes([
1291 buf[*pos],
1292 buf[*pos + 1],
1293 buf[*pos + 2],
1294 buf[*pos + 3],
1295 buf[*pos + 4],
1296 buf[*pos + 5],
1297 buf[*pos + 6],
1298 buf[*pos + 7],
1299 ]);
1300 *pos += 8;
1301 Value::BigInt(val)
1302 }
1303 44 => {
1304 let col_len = Self::read_varu32_safe(buf, pos)?;
1305 let col = String::from_utf8(buf[*pos..*pos + col_len].to_vec())?;
1306 *pos += col_len;
1307 let key_len = Self::read_varu32_safe(buf, pos)?;
1308 let key = String::from_utf8(buf[*pos..*pos + key_len].to_vec())?;
1309 *pos += key_len;
1310 Value::KeyRef(col, key)
1311 }
1312 45 => {
1313 let col_len = Self::read_varu32_safe(buf, pos)?;
1314 let col = String::from_utf8(buf[*pos..*pos + col_len].to_vec())?;
1315 *pos += col_len;
1316 let id = u64::from_le_bytes([
1317 buf[*pos],
1318 buf[*pos + 1],
1319 buf[*pos + 2],
1320 buf[*pos + 3],
1321 buf[*pos + 4],
1322 buf[*pos + 5],
1323 buf[*pos + 6],
1324 buf[*pos + 7],
1325 ]);
1326 *pos += 8;
1327 Value::DocRef(col, id)
1328 }
1329 46 => {
1330 let len = Self::read_varu32_safe(buf, pos)?;
1331 let name = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1332 *pos += len;
1333 Value::TableRef(name)
1334 }
1335 47 => {
1336 let val =
1337 u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1338 *pos += 4;
1339 Value::PageRef(val)
1340 }
1341 48 => {
1342 let len = Self::read_varu32_safe(buf, pos)?;
1343 let bytes = buf[*pos..*pos + len].to_vec();
1344 *pos += len;
1345 Value::Secret(bytes)
1346 }
1347 49 => {
1348 let len = Self::read_varu32_safe(buf, pos)?;
1349 let hash = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1350 *pos += len;
1351 Value::Password(hash)
1352 }
1353 50 => {
1354 let len = Self::read_varu32_safe(buf, pos)?;
1355 let code = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1356 *pos += len;
1357 Value::AssetCode(code)
1358 }
1359 51 => {
1360 let len = Self::read_varu32_safe(buf, pos)?;
1361 let asset_code = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1362 *pos += len;
1363 let scale = buf[*pos];
1364 *pos += 1;
1365 let minor_units = i64::from_le_bytes([
1366 buf[*pos],
1367 buf[*pos + 1],
1368 buf[*pos + 2],
1369 buf[*pos + 3],
1370 buf[*pos + 4],
1371 buf[*pos + 5],
1372 buf[*pos + 6],
1373 buf[*pos + 7],
1374 ]);
1375 *pos += 8;
1376 Value::Money {
1377 asset_code,
1378 minor_units,
1379 scale,
1380 }
1381 }
1382 52 => {
1383 let len = Self::read_varu32_safe(buf, pos)?;
1384 let value = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1385 *pos += len;
1386 Value::DecimalText(value)
1387 }
1388 0x85 => {
1390 let orig_len = Self::read_varu32_safe(buf, pos)?;
1391 let comp_len = Self::read_varu32_safe(buf, pos)?;
1392 let compressed = &buf[*pos..*pos + comp_len];
1393 *pos += comp_len;
1394 let mut out = vec![0u8; orig_len];
1395 zstd::bulk::decompress_to_buffer(compressed, &mut out)
1396 .map_err(|e| format!("C3 Text decompress: {e}"))?;
1397 Value::text(String::from_utf8(out).map_err(|e| format!("C3 Text UTF-8: {e}"))?)
1398 }
1399 0x86 => {
1400 let orig_len = Self::read_varu32_safe(buf, pos)?;
1401 let comp_len = Self::read_varu32_safe(buf, pos)?;
1402 let compressed = &buf[*pos..*pos + comp_len];
1403 *pos += comp_len;
1404 let mut out = vec![0u8; orig_len];
1405 zstd::bulk::decompress_to_buffer(compressed, &mut out)
1406 .map_err(|e| format!("C3 Blob decompress: {e}"))?;
1407 Value::Blob(out)
1408 }
1409 _ => return Err(format!("Unknown Value type: {}", type_byte).into()),
1410 })
1411 }
1412
1413 fn write_value_binary(buf: &mut Vec<u8>, value: &Value) {
1418 use std::net::IpAddr;
1419
1420 match value {
1421 Value::Null => buf.push(0),
1422 Value::Boolean(b) => {
1423 buf.push(1);
1424 buf.push(if *b { 1 } else { 0 });
1425 }
1426 Value::Integer(i) => {
1427 buf.push(2);
1428 buf.extend_from_slice(&i.to_le_bytes());
1429 }
1430 Value::UnsignedInteger(u) => {
1431 buf.push(3);
1432 buf.extend_from_slice(&u.to_le_bytes());
1433 }
1434 Value::Float(f) => {
1435 buf.push(4);
1436 buf.extend_from_slice(&f.to_le_bytes());
1437 }
1438 Value::Text(s) => {
1439 const TEXT_COMPRESS_THRESHOLD: usize = 2048;
1443 if s.len() >= TEXT_COMPRESS_THRESHOLD {
1444 if let Ok(compressed) = zstd::bulk::compress(s.as_bytes(), 3) {
1445 if compressed.len() < s.len() {
1446 buf.push(0x85); write_varu32(buf, s.len() as u32); write_varu32(buf, compressed.len() as u32);
1449 buf.extend_from_slice(&compressed);
1450 return; }
1452 }
1453 }
1454 buf.push(5);
1455 write_varu32(buf, s.len() as u32);
1456 buf.extend_from_slice(s.as_bytes());
1457 }
1458 Value::Blob(bytes) => {
1459 const BLOB_COMPRESS_THRESHOLD: usize = 2048;
1462 if bytes.len() >= BLOB_COMPRESS_THRESHOLD {
1463 if let Ok(compressed) = zstd::bulk::compress(bytes.as_slice(), 3) {
1464 if compressed.len() < bytes.len() {
1465 buf.push(0x86); write_varu32(buf, bytes.len() as u32);
1467 write_varu32(buf, compressed.len() as u32);
1468 buf.extend_from_slice(&compressed);
1469 return;
1470 }
1471 }
1472 }
1473 buf.push(6);
1474 write_varu32(buf, bytes.len() as u32);
1475 buf.extend_from_slice(bytes);
1476 }
1477 Value::Timestamp(t) => {
1478 buf.push(7);
1479 buf.extend_from_slice(&t.to_le_bytes());
1480 }
1481 Value::Duration(d) => {
1482 buf.push(8);
1483 buf.extend_from_slice(&d.to_le_bytes());
1484 }
1485 Value::IpAddr(ip) => {
1486 buf.push(9);
1487 match ip {
1488 IpAddr::V4(v4) => {
1489 buf.push(4);
1490 buf.extend_from_slice(&v4.octets());
1491 }
1492 IpAddr::V6(v6) => {
1493 buf.push(6);
1494 buf.extend_from_slice(&v6.octets());
1495 }
1496 }
1497 }
1498 Value::MacAddr(mac) => {
1499 buf.push(10);
1500 buf.extend_from_slice(mac);
1501 }
1502 Value::Vector(vec) => {
1503 buf.push(11);
1504 write_varu32(buf, vec.len() as u32);
1505 for f in vec {
1506 buf.extend_from_slice(&f.to_le_bytes());
1507 }
1508 }
1509 Value::Json(bytes) => {
1510 buf.push(12);
1511 write_varu32(buf, bytes.len() as u32);
1512 buf.extend_from_slice(bytes);
1513 }
1514 Value::Uuid(uuid) => {
1515 buf.push(13);
1516 buf.extend_from_slice(uuid);
1517 }
1518 Value::NodeRef(s) => {
1519 buf.push(14);
1520 write_varu32(buf, s.len() as u32);
1521 buf.extend_from_slice(s.as_bytes());
1522 }
1523 Value::EdgeRef(s) => {
1524 buf.push(15);
1525 write_varu32(buf, s.len() as u32);
1526 buf.extend_from_slice(s.as_bytes());
1527 }
1528 Value::VectorRef(s, id) => {
1529 buf.push(16);
1530 write_varu32(buf, s.len() as u32);
1531 buf.extend_from_slice(s.as_bytes());
1532 buf.extend_from_slice(&id.to_le_bytes());
1533 }
1534 Value::RowRef(s, id) => {
1535 buf.push(17);
1536 write_varu32(buf, s.len() as u32);
1537 buf.extend_from_slice(s.as_bytes());
1538 buf.extend_from_slice(&id.to_le_bytes());
1539 }
1540 Value::Color(rgb) => {
1541 buf.push(18);
1542 buf.extend_from_slice(rgb);
1543 }
1544 Value::Email(s) => {
1545 buf.push(19);
1546 write_varu32(buf, s.len() as u32);
1547 buf.extend_from_slice(s.as_bytes());
1548 }
1549 Value::Url(s) => {
1550 buf.push(20);
1551 write_varu32(buf, s.len() as u32);
1552 buf.extend_from_slice(s.as_bytes());
1553 }
1554 Value::Phone(n) => {
1555 buf.push(21);
1556 buf.extend_from_slice(&n.to_le_bytes());
1557 }
1558 Value::Semver(v) => {
1559 buf.push(22);
1560 buf.extend_from_slice(&v.to_le_bytes());
1561 }
1562 Value::Cidr(ip, prefix) => {
1563 buf.push(23);
1564 buf.extend_from_slice(&ip.to_le_bytes());
1565 buf.push(*prefix);
1566 }
1567 Value::Date(d) => {
1568 buf.push(24);
1569 buf.extend_from_slice(&d.to_le_bytes());
1570 }
1571 Value::Time(t) => {
1572 buf.push(25);
1573 buf.extend_from_slice(&t.to_le_bytes());
1574 }
1575 Value::Decimal(v) => {
1576 buf.push(26);
1577 buf.extend_from_slice(&v.to_le_bytes());
1578 }
1579 Value::EnumValue(i) => {
1580 buf.push(27);
1581 buf.push(*i);
1582 }
1583 Value::Array(elems) => {
1584 buf.push(28);
1585 write_varu32(buf, elems.len() as u32);
1586 for elem in elems {
1587 Self::write_value_binary(buf, elem);
1588 }
1589 }
1590 Value::TimestampMs(v) => {
1591 buf.push(29);
1592 buf.extend_from_slice(&v.to_le_bytes());
1593 }
1594 Value::Ipv4(v) => {
1595 buf.push(30);
1596 buf.extend_from_slice(&v.to_le_bytes());
1597 }
1598 Value::Ipv6(bytes) => {
1599 buf.push(31);
1600 buf.extend_from_slice(bytes);
1601 }
1602 Value::Subnet(ip, mask) => {
1603 buf.push(32);
1604 buf.extend_from_slice(&ip.to_le_bytes());
1605 buf.extend_from_slice(&mask.to_le_bytes());
1606 }
1607 Value::Port(v) => {
1608 buf.push(33);
1609 buf.extend_from_slice(&v.to_le_bytes());
1610 }
1611 Value::Latitude(v) => {
1612 buf.push(34);
1613 buf.extend_from_slice(&v.to_le_bytes());
1614 }
1615 Value::Longitude(v) => {
1616 buf.push(35);
1617 buf.extend_from_slice(&v.to_le_bytes());
1618 }
1619 Value::GeoPoint(lat, lon) => {
1620 buf.push(36);
1621 buf.extend_from_slice(&lat.to_le_bytes());
1622 buf.extend_from_slice(&lon.to_le_bytes());
1623 }
1624 Value::Country2(c) => {
1625 buf.push(37);
1626 buf.extend_from_slice(c);
1627 }
1628 Value::Country3(c) => {
1629 buf.push(38);
1630 buf.extend_from_slice(c);
1631 }
1632 Value::Lang2(c) => {
1633 buf.push(39);
1634 buf.extend_from_slice(c);
1635 }
1636 Value::Lang5(c) => {
1637 buf.push(40);
1638 buf.extend_from_slice(c);
1639 }
1640 Value::Currency(c) => {
1641 buf.push(41);
1642 buf.extend_from_slice(c);
1643 }
1644 Value::AssetCode(code) => {
1645 buf.push(50);
1646 write_varu32(buf, code.len() as u32);
1647 buf.extend_from_slice(code.as_bytes());
1648 }
1649 Value::Money {
1650 asset_code,
1651 minor_units,
1652 scale,
1653 } => {
1654 buf.push(51);
1655 write_varu32(buf, asset_code.len() as u32);
1656 buf.extend_from_slice(asset_code.as_bytes());
1657 buf.push(*scale);
1658 buf.extend_from_slice(&minor_units.to_le_bytes());
1659 }
1660 Value::ColorAlpha(rgba) => {
1661 buf.push(42);
1662 buf.extend_from_slice(rgba);
1663 }
1664 Value::BigInt(v) => {
1665 buf.push(43);
1666 buf.extend_from_slice(&v.to_le_bytes());
1667 }
1668 Value::KeyRef(col, key) => {
1669 buf.push(44);
1670 write_varu32(buf, col.len() as u32);
1671 buf.extend_from_slice(col.as_bytes());
1672 write_varu32(buf, key.len() as u32);
1673 buf.extend_from_slice(key.as_bytes());
1674 }
1675 Value::DocRef(col, id) => {
1676 buf.push(45);
1677 write_varu32(buf, col.len() as u32);
1678 buf.extend_from_slice(col.as_bytes());
1679 buf.extend_from_slice(&id.to_le_bytes());
1680 }
1681 Value::TableRef(name) => {
1682 buf.push(46);
1683 write_varu32(buf, name.len() as u32);
1684 buf.extend_from_slice(name.as_bytes());
1685 }
1686 Value::PageRef(page_id) => {
1687 buf.push(47);
1688 buf.extend_from_slice(&page_id.to_le_bytes());
1689 }
1690 Value::Secret(bytes) => {
1691 buf.push(48);
1692 write_varu32(buf, bytes.len() as u32);
1693 buf.extend_from_slice(bytes);
1694 }
1695 Value::Password(hash) => {
1696 buf.push(49);
1697 write_varu32(buf, hash.len() as u32);
1698 buf.extend_from_slice(hash.as_bytes());
1699 }
1700 Value::DecimalText(value) => {
1701 buf.push(52);
1702 write_varu32(buf, value.len() as u32);
1703 buf.extend_from_slice(value.as_bytes());
1704 }
1705 }
1706 }
1707}
1708
1709#[cfg(test)]
1710mod aux_metadata_dump_tests {
1711 use super::*;
1712 use std::collections::HashMap;
1713
1714 #[test]
1715 fn snapshot_load_preserves_existing_wal_and_restores_future_appends() {
1716 let directory = tempfile::tempdir().expect("directory");
1717 let path = directory.path().join("snapshot.rdb");
1718 let store = UnifiedStore::with_config(UnifiedStoreConfig::default());
1719 let old =
1720 UnifiedEntity::table_row(EntityId::new(1), "rows", 1, vec![Value::text("snapshot")]);
1721 store.insert_auto("rows", old).expect("snapshot row");
1722 let snapshot = store.to_binary_dump_bytes();
1723 crate::storage::EmbeddedRdbArtifact::create_with_snapshot(&path, &snapshot)
1724 .expect("artifact");
1725 let newer = UnifiedEntity::table_row(
1726 EntityId::new(1),
1727 "rows",
1728 1,
1729 vec![Value::text("durable WAL")],
1730 );
1731 let action = StoreWalAction::upsert_entity("rows", &newer, None, STORE_VERSION_CURRENT);
1732 UnifiedStore::with_config(UnifiedStoreConfig::default().with_embedded_wal_path(&path))
1733 .finish_paged_write([action])
1734 .expect("durable update after snapshot");
1735 let before = std::fs::read(&path).expect("original artifact bytes");
1736 let loaded = UnifiedStore::load_from_bytes_with_config(
1737 &snapshot,
1738 UnifiedStoreConfig::default().with_embedded_wal_path(&path),
1739 )
1740 .expect("reconstruct snapshot");
1741 assert_eq!(std::fs::read(&path).expect("artifact after load"), before);
1742 assert_eq!(
1743 loaded.config().embedded_wal_path.as_deref(),
1744 Some(path.as_path())
1745 );
1746
1747 let generation = crate::storage::EmbeddedRdbArtifact::open(&path)
1748 .expect("boundary before future write")
1749 .selected_superblock
1750 .generation;
1751 loaded
1752 .insert_auto(
1753 "rows",
1754 UnifiedEntity::table_row(
1755 EntityId::new(2),
1756 "rows",
1757 2,
1758 vec![Value::text("future write")],
1759 ),
1760 )
1761 .expect("future writes still append WAL");
1762 assert_eq!(
1763 crate::storage::EmbeddedRdbArtifact::open(&path)
1764 .expect("boundary after future write")
1765 .selected_superblock
1766 .generation,
1767 generation + 1
1768 );
1769 }
1770
1771 #[test]
1772 fn aux_metadata_round_trips_through_binary_dump() {
1773 let store = UnifiedStore::with_config(UnifiedStoreConfig::default());
1774 store.create_collection("k").expect("create collection");
1775 store.set_aux_metadata(b"contract-blob".to_vec());
1776
1777 let bytes = store.to_binary_dump_bytes();
1778 let reloaded = UnifiedStore::load_from_bytes(&bytes).expect("reload dump");
1779
1780 assert_eq!(reloaded.aux_metadata(), b"contract-blob".to_vec());
1781 }
1782
1783 #[test]
1784 fn empty_aux_metadata_round_trips_as_empty() {
1785 let store = UnifiedStore::with_config(UnifiedStoreConfig::default());
1786 let bytes = store.to_binary_dump_bytes();
1787 let reloaded = UnifiedStore::load_from_bytes(&bytes).expect("reload dump");
1788
1789 assert!(reloaded.aux_metadata().is_empty());
1790 }
1791
1792 #[test]
1793 fn interned_timeseries_series_reduces_repeated_tag_storage() {
1794 let tags = HashMap::from([
1795 (
1796 "host".to_string(),
1797 "srv-very-long-hostname-0001".to_string(),
1798 ),
1799 ("region".to_string(), "us-east-1".to_string()),
1800 ("service".to_string(), "checkout-api".to_string()),
1801 ]);
1802 let inline_bytes: usize = (0..100)
1803 .map(|timestamp_ns| {
1804 let entity = UnifiedEntity::new(
1805 EntityId::new(0),
1806 EntityKind::TimeSeriesPoint(Box::new(TimeSeriesPointKind {
1807 series: "cpu_metrics".to_string(),
1808 metric: "cpu.idle".to_string(),
1809 })),
1810 EntityData::TimeSeries(crate::storage::TimeSeriesData {
1811 metric: "cpu.idle".to_string(),
1812 series_id: None,
1813 timestamp_ns,
1814 value: 94.8,
1815 tags: tags.clone(),
1816 fields: HashMap::new(),
1817 }),
1818 );
1819 UnifiedStore::serialize_entity_record(&entity, None, reddb_file::STORE_VERSION_V10)
1820 .len()
1821 })
1822 .sum();
1823
1824 let mut dictionary_fields = HashMap::new();
1825 dictionary_fields.insert("collection".to_string(), Value::text("cpu_metrics"));
1826 dictionary_fields.insert("series_id".to_string(), Value::UnsignedInteger(0));
1827 dictionary_fields.insert("metric".to_string(), Value::text("cpu.idle"));
1828 dictionary_fields.insert(
1829 "canonical_tags".to_string(),
1830 Value::text(
1831 r#"{"host":"srv-very-long-hostname-0001","region":"us-east-1","service":"checkout-api"}"#,
1832 ),
1833 );
1834 dictionary_fields.insert(
1835 "tags".to_string(),
1836 Value::Json(
1837 br#"{"host":"srv-very-long-hostname-0001","region":"us-east-1","service":"checkout-api"}"#
1838 .to_vec(),
1839 ),
1840 );
1841 let dictionary = UnifiedEntity::new(
1842 EntityId::new(0),
1843 EntityKind::TableRow {
1844 table: Arc::from("red_timeseries_series"),
1845 row_id: 0,
1846 },
1847 EntityData::Row(RowData {
1848 columns: Vec::new(),
1849 named: Some(dictionary_fields),
1850 schema: None,
1851 }),
1852 );
1853 let dictionary_bytes =
1854 UnifiedStore::serialize_entity_record(&dictionary, None, STORE_VERSION_V11).len();
1855 let interned_bytes = dictionary_bytes
1856 + (0..100)
1857 .map(|timestamp_ns| {
1858 let entity = UnifiedEntity::new(
1859 EntityId::new(0),
1860 EntityKind::TimeSeriesPoint(Box::new(TimeSeriesPointKind {
1861 series: "cpu_metrics".to_string(),
1862 metric: "cpu.idle".to_string(),
1863 })),
1864 EntityData::TimeSeries(crate::storage::TimeSeriesData {
1865 metric: "cpu.idle".to_string(),
1866 series_id: Some(0),
1867 timestamp_ns,
1868 value: 94.8,
1869 tags: HashMap::new(),
1870 fields: HashMap::new(),
1871 }),
1872 );
1873 UnifiedStore::serialize_entity_record(&entity, None, STORE_VERSION_V11).len()
1874 })
1875 .sum::<usize>();
1876
1877 assert!(
1878 interned_bytes < inline_bytes,
1879 "interned series storage should shrink repeated tags: inline={inline_bytes}, interned={interned_bytes}"
1880 );
1881 }
1882}