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